/*-------------------------------------------------------------------------
 *
 * index.c
 *	  创建和销毁POSTGRES索引关系的代码
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/catalog/index.c
 *
 * INTERFACE ROUTINES
 *		index_create()			- 创建一个已记录的索引关系
 *		index_drop()			- 从目录中删除索引关系
 *		BuildIndexInfo()		- 准备插入索引元组
 *		FormIndexDatum()		- 构造单个索引元组的datum向量
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include <unistd.h>

#include "access/amapi.h"
#include "access/heapam.h"
#include "access/multixact.h"
#include "access/reloptions.h"
#include "access/relscan.h"
#include "access/sysattr.h"
#include "access/tableam.h"
#include "access/toast_compression.h"
#include "access/transam.h"
#include "access/visibilitymap.h"
#include "access/xact.h"
#include "bootstrap/bootstrap.h"
#include "catalog/binary_upgrade.h"
#include "catalog/catalog.h"
#include "catalog/dependency.h"
#include "catalog/heap.h"
#include "catalog/index.h"
#include "catalog/objectaccess.h"
#include "catalog/partition.h"
#include "catalog/pg_am.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_constraint.h"
#include "catalog/pg_depend.h"
#include "catalog/pg_description.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_opclass.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_trigger.h"
#include "catalog/pg_type.h"
#include "catalog/storage.h"
#include "catalog/storage_xlog.h"
#include "commands/event_trigger.h"
#include "commands/progress.h"
#include "commands/tablecmds.h"
#include "commands/tablespace.h"
#include "commands/trigger.h"
#include "executor/executor.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
#include "parser/parser.h"
#include "pgstat.h"
#include "rewrite/rewriteManip.h"
#include "storage/bufmgr.h"
#include "storage/lmgr.h"
#include "storage/predicate.h"
#include "storage/procarray.h"
#include "storage/smgr.h"
#include "utils/builtins.h"
#include "utils/datum.h"
#include "utils/fmgroids.h"
#include "utils/guc.h"
#include "utils/inval.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/pg_rusage.h"
#include "utils/rel.h"
#include "utils/snapmgr.h"
#include "utils/syscache.h"
#include "utils/tuplesort.h"

/* 可能由 pg_upgrade_support 函数设置 */
Oid			binary_upgrade_next_index_pg_class_oid = InvalidOid;
Oid			binary_upgrade_next_index_pg_class_relfilenode = InvalidOid;

/*
 * 无指针表示法，用于在重新编制系统目录索引时使用的变量；
 * 我们使用它来将这些值传播到并行工作进程。
 */
typedef struct
{
	Oid			currentlyReindexedHeap;
	Oid			currentlyReindexedIndex;
	int			numPendingReindexedIndexes;
	Oid			pendingReindexedIndexes[FLEXIBLE_ARRAY_MEMBER];
} SerializedReindexState;

/* 非导出函数原型 */
static bool fc_relationHasPrimaryKey(Relation fc_rel);
static TupleDesc fc_ConstructTupleDescriptor(Relation fc_heapRelation,
										  IndexInfo *fc_indexInfo,
										  List *fc_indexColNames,
										  Oid fc_accessMethodObjectId,
										  Oid *fc_collationObjectId,
										  Oid *fc_classObjectId);
static void fc_InitializeAttributeOids(Relation fc_indexRelation,
									int fc_numatts, Oid fc_indexoid);
static void fc_AppendAttributeTuples(Relation fc_indexRelation, Datum *fc_attopts);
static void fc_UpdateIndexRelation(Oid fc_indexoid, Oid fc_heapoid,
								Oid fc_parentIndexId,
								IndexInfo *fc_indexInfo,
								Oid *fc_collationOids,
								Oid *fc_classOids,
								int16 *fc_coloptions,
								bool fc_primary,
								bool fc_isexclusion,
								bool fc_immediate,
								bool fc_isvalid,
								bool fc_isready);
static void fc_index_update_stats(Relation fc_rel,
							   bool fc_hasindex,
							   double fc_reltuples);
static void fc_IndexCheckExclusion(Relation fc_heapRelation,
								Relation fc_indexRelation,
								IndexInfo *fc_indexInfo);
static bool fc_validate_index_callback(ItemPointer fc_itemptr, void *fc_opaque);
static bool fc_ReindexIsCurrentlyProcessingIndex(Oid fc_indexOid);
static void fc_SetReindexProcessing(Oid fc_heapOid, Oid fc_indexOid);
static void fc_ResetReindexProcessing(void);
static void fc_SetReindexPending(List *fc_indexes);
static void fc_RemoveReindexPending(Oid fc_indexOid);


/*
 * relationHasPrimaryKey
 *		查看现有关系是否有主键。
 *
 * 调用者必须对该关系有适当的锁定。
 *
 * 注意：我们故意不在此检查 indisvalid；这是因为
 * 这用于强制规则：只能有一个 indisprimary 索引，
 * 我们希望即使该索引无效，这一规则依然成立。
 */
static bool fc_relationHasPrimaryKey(Relation fc_rel)
{
	bool		fc_result = false;
	List	   *fc_indexoidlist;
	ListCell   *fc_indexoidscan;

	/*
	 * 从 relcache 获取表的索引 OID 列表，并在 pg_index 系统缓存中查找
	 * 每一个，直到找到一个标记为主键的索引
	 * （希望不会超过一个这样的索引）。
	 */
	fc_indexoidlist = RelationGetIndexList(fc_rel);

	foreach(fc_indexoidscan, fc_indexoidlist)
	{
		Oid			fc_indexoid = lfirst_oid(fc_indexoidscan);
		HeapTuple	fc_indexTuple;

		fc_indexTuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(fc_indexoid));
		if (!HeapTupleIsValid(fc_indexTuple))	/* 不应该发生 */
			elog(ERROR, "cache lookup failed for index %u", fc_indexoid);
		fc_result = ((Form_pg_index) GETSTRUCT(fc_indexTuple))->indisprimary;
		ReleaseSysCache(fc_indexTuple);
		if (fc_result)
			break;
	}

	list_free(fc_indexoidlist);

	return fc_result;
}

/*
 * index_check_primary_key
 *		在创建 PRIMARY KEY 索引之前应用所需的特殊检查
 *
 * 这项处理曾经在 DefineIndex() 中，但已经拆分出来
 * 以便在 ALTER TABLE ADD PRIMARY KEY USING INDEX 期间应用。
 *
 * 我们检查是否存在先前的主键，所有索引的列
 * 都是简单列引用（而不是表达式），并且所有这些
 * 列都标记为 NOT NULL。如果不是，则失败。
 *
 * 我们过去会通过自己执行本地 ALTER TABLE 命令来自动将未标记的列更改为 NOT NULL。
 * 但如果我们执行 ALTER TABLE 的一个子命令，这样做效果不佳：操作可能没有以正确的顺序执行。
 * 现在我们期望解析器在尝试创建主键索引之前插入任何所需的 ALTER TABLE SET NOT NULL 操作。
 *
 * 调用者最好对表至少有 ShareLock，否则不可信的非空
 * 检查。
 */
void index_check_primary_key(Relation fc_heapRel,
						IndexInfo *fc_indexInfo,
						bool fc_is_alter_table,
						IndexStmt *fc_stmt)
{
	int			fc_i;

	/*
	 * 如果 ALTER TABLE 或 CREATE TABLE .. PARTITION OF，检查是否已经
	 * 存在一个主键。在 CREATE TABLE 的普通关系中，我们
	 * 相信解析器拒绝了多个 pkey 子句；而 CREATE
	 * INDEX 没办法声明 PRIMARY KEY，因此也没有问题。
	 */
	if ((fc_is_alter_table || fc_heapRel->rd_rel->relispartition) &&
		fc_relationHasPrimaryKey(fc_heapRel))
	{
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
				 errmsg("multiple primary keys for table \"%s\" are not allowed",
						RelationGetRelationName(fc_heapRel))));
	}

	/*
	 * 检查主键中的所有属性是否被标记为非空。
	 * （我们并不真正期望看到这一点；这意味着解析器出错了。
	 * 但无论如何检查是明智的。）
	 */
	for (fc_i = 0; fc_i < fc_indexInfo->ii_NumIndexKeyAttrs; fc_i++)
	{
		AttrNumber	fc_attnum = fc_indexInfo->ii_IndexAttrNumbers[fc_i];
		HeapTuple	fc_atttuple;
		Form_pg_attribute fc_attform;

		if (fc_attnum == 0)
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("primary keys cannot be expressions")));

		/* 系统属性永远不会为 null，所以无需检查 */
		if (fc_attnum < 0)
			continue;

		fc_atttuple = SearchSysCache2(ATTNUM,
								   ObjectIdGetDatum(RelationGetRelid(fc_heapRel)),
								   Int16GetDatum(fc_attnum));
		if (!HeapTupleIsValid(fc_atttuple))
			elog(ERROR, "cache lookup failed for attribute %d of relation %u",
				 fc_attnum, RelationGetRelid(fc_heapRel));
		fc_attform = (Form_pg_attribute) GETSTRUCT(fc_atttuple);

		if (!fc_attform->attnotnull)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
					 errmsg("primary key column \"%s\" is not marked NOT NULL",
							NameStr(fc_attform->attname))));

		ReleaseSysCache(fc_atttuple);
	}
}

/*
 *		构建 TupleDescriptor
 *
 * 为新索引构建索引元组描述符
 */
static TupleDesc fc_ConstructTupleDescriptor(Relation fc_heapRelation,
						 IndexInfo *fc_indexInfo,
						 List *fc_indexColNames,
						 Oid fc_accessMethodObjectId,
						 Oid *fc_collationObjectId,
						 Oid *fc_classObjectId)
{
	int			fc_numatts = fc_indexInfo->ii_NumIndexAttrs;
	int			fc_numkeyatts = fc_indexInfo->ii_NumIndexKeyAttrs;
	ListCell   *fc_colnames_item = list_head(fc_indexColNames);
	ListCell   *fc_indexpr_item = list_head(fc_indexInfo->ii_Expressions);
	IndexAmRoutine *fc_amroutine;
	TupleDesc	fc_heapTupDesc;
	TupleDesc	fc_indexTupDesc;
	int			fc_natts;			/* 堆关系中的属性数量 --- 用于错误检查 */
	int			fc_i;

	/* 我们需要访问索引 AM 的 API 结构 */
	fc_amroutine = GetIndexAmRoutineByAmId(fc_accessMethodObjectId, false);

	/* ... 以及表的元组描述符 */
	fc_heapTupDesc = RelationGetDescr(fc_heapRelation);
	fc_natts = RelationGetForm(fc_heapRelation)->relnatts;

	/*
	 * 分配新的元组描述符
	 */
	fc_indexTupDesc = CreateTemplateTupleDesc(fc_numatts);

	/*
	 * 填充 pg_attribute 行。
	 */
	for (fc_i = 0; fc_i < fc_numatts; fc_i++)
	{
		AttrNumber	fc_atnum = fc_indexInfo->ii_IndexAttrNumbers[fc_i];
		Form_pg_attribute fc_to = TupleDescAttr(fc_indexTupDesc, fc_i);
		HeapTuple	fc_tuple;
		Form_pg_type fc_typeTup;
		Form_pg_opclass fc_opclassTup;
		Oid			fc_keyType;

		MemSet(fc_to, 0, ATTRIBUTE_FIXED_PART_SIZE);
		fc_to->attnum = fc_i + 1;
		fc_to->attstattarget = -1;
		fc_to->attcacheoff = -1;
		fc_to->attislocal = true;
		fc_to->attcollation = (fc_i < fc_numkeyatts) ?
			fc_collationObjectId[fc_i] : InvalidOid;

		/*
		 * 按调用者指定的内容设置属性名称。
		 */
		if (fc_colnames_item == NULL)	/* 不应该发生 */
			elog(ERROR, "too few entries in colnames list");
		namestrcpy(&fc_to->attname, (const char *) lfirst(fc_colnames_item));
		fc_colnames_item = lnext(fc_indexColNames, fc_colnames_item);

		/*
		 * 对于简单索引列，我们从父关系中复制一些 pg_attribute 字段。
		 * 对于表达式，我们必须查看表达式结果。
		 */
		if (fc_atnum != 0)
		{
			/* 简单索引列 */
			const FormData_pg_attribute *fc_from;

			Assert(fc_atnum > 0);	/* 应该在上述内容中捕获 */

			if (fc_atnum > fc_natts)	/* 安全检查 */
				elog(ERROR, "invalid column number %d", fc_atnum);
			fc_from = TupleDescAttr(fc_heapTupDesc,
								 AttrNumberGetAttrOffset(fc_atnum));

			fc_to->atttypid = fc_from->atttypid;
			fc_to->attlen = fc_from->attlen;
			fc_to->attndims = fc_from->attndims;
			fc_to->atttypmod = fc_from->atttypmod;
			fc_to->attbyval = fc_from->attbyval;
			fc_to->attalign = fc_from->attalign;
			fc_to->attstorage = fc_from->attstorage;
			fc_to->attcompression = fc_from->attcompression;
		}
		else
		{
			/* 表达式索引 */
			Node	   *fc_indexkey;

			if (fc_indexpr_item == NULL)	/* 不应该发生 */
				elog(ERROR, "too few entries in indexprs list");
			fc_indexkey = (Node *) lfirst(fc_indexpr_item);
			fc_indexpr_item = lnext(fc_indexInfo->ii_Expressions, fc_indexpr_item);

			/*
			 * 在 pg_type 中查找表达式类型以获取类型长度等。
			 */
			fc_keyType = exprType(fc_indexkey);
			fc_tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(fc_keyType));
			if (!HeapTupleIsValid(fc_tuple))
				elog(ERROR, "cache lookup failed for type %u", fc_keyType);
			fc_typeTup = (Form_pg_type) GETSTRUCT(fc_tuple);

			/*
			 * 分配一些属性值，其他的留着。
			 */
			fc_to->atttypid = fc_keyType;
			fc_to->attlen = fc_typeTup->typlen;
			fc_to->atttypmod = exprTypmod(fc_indexkey);
			fc_to->attbyval = fc_typeTup->typbyval;
			fc_to->attalign = fc_typeTup->typalign;
			fc_to->attstorage = fc_typeTup->typstorage;

			/*
			 * 对于表达式列，设置 attcompression 为无效，因为
			 * 没有来自于复制值的表列。无论何时
			 * 我们实际需要压缩一个值，我们将使用当前
			 * default_toast_compression 的值。
			 */
			fc_to->attcompression = InvalidCompressionMethod;

			ReleaseSysCache(fc_tuple);

			/*
			 * 确保表达式生成的类型是安全存储在
			 * 索引中的。我们需要这个防御，因为我们有伪类型的索引操作类
			 * ，如“record”，而实际存储的类型最好是安全的；
			 * 例如，一个命名的复合类型是可以的，一个
			 * 匿名记录类型则不可以。该测试与
			 * 是否表列是安全类型有关（这就是为什么我们
			 * 不需要检查非表达式情况）。
			 */
			CheckAttributeType(NameStr(fc_to->attname),
							   fc_to->atttypid, fc_to->attcollation,
							   NIL, 0);
		}

		/*
		 * 我们还没有索引的正确关系 OID，因此暂时
		 * 将其设置为无效。InitializeAttributeOids() 会在稍后修复它。
		 */
		fc_to->attrelid = InvalidOid;

		/*
		 * 检查操作类和索引 AM 以查看是否提供了 keytype
		 * （覆盖属性类型）。操作类（如果存在）优先。
		 */
		fc_keyType = fc_amroutine->amkeytype;

		if (fc_i < fc_indexInfo->ii_NumIndexKeyAttrs)
		{
			fc_tuple = SearchSysCache1(CLAOID, ObjectIdGetDatum(fc_classObjectId[fc_i]));
			if (!HeapTupleIsValid(fc_tuple))
				elog(ERROR, "cache lookup failed for opclass %u",
					 fc_classObjectId[fc_i]);
			fc_opclassTup = (Form_pg_opclass) GETSTRUCT(fc_tuple);
			if (OidIsValid(fc_opclassTup->opckeytype))
				fc_keyType = fc_opclassTup->opckeytype;

			/*
			 * 如果 keytype 被指定为 ANYELEMENT，且 opcintype 是
			 * ANYARRAY，则属性类型必须是数组（否则将无法匹配
			 * 此操作类）；使用其元素类型。
			 *
			 * 我们也可以允许在这里使用 ANYCOMPATIBLE/ANYCOMPATIBLEARRAY，
			 * 但似乎没有必要；没有理由将操作类声明为使用 ANYCOMPATIBLEARRAY 而不是 ANYARRAY。
			 */
			if (fc_keyType == ANYELEMENTOID && fc_opclassTup->opcintype == ANYARRAYOID)
			{
				fc_keyType = get_base_element_type(fc_to->atttypid);
				if (!OidIsValid(fc_keyType))
					elog(ERROR, "could not get element type of array type %u",
						 fc_to->atttypid);
			}

			ReleaseSysCache(fc_tuple);
		}

		/*
		 * 如果指定了不同于堆值的键类型，请更新
		 * 索引 tupdesc 中的类型相关字段。
		 */
		if (OidIsValid(fc_keyType) && fc_keyType != fc_to->atttypid)
		{
			fc_tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(fc_keyType));
			if (!HeapTupleIsValid(fc_tuple))
				elog(ERROR, "cache lookup failed for type %u", fc_keyType);
			fc_typeTup = (Form_pg_type) GETSTRUCT(fc_tuple);

			fc_to->atttypid = fc_keyType;
			fc_to->atttypmod = -1;
			fc_to->attlen = fc_typeTup->typlen;
			fc_to->attbyval = fc_typeTup->typbyval;
			fc_to->attalign = fc_typeTup->typalign;
			fc_to->attstorage = fc_typeTup->typstorage;
			/* 与上面相同，在这种情况下使用默认的压缩方法 */
			fc_to->attcompression = InvalidCompressionMethod;

			ReleaseSysCache(fc_tuple);
		}
	}

	pfree(fc_amroutine);

	return fc_indexTupDesc;
}

/* ----------------------------------------------------------------
 *		InitializeAttributeOids
 * ----------------------------------------------------------------
 */
static void fc_InitializeAttributeOids(Relation fc_indexRelation,
						int fc_numatts,
						Oid fc_indexoid)
{
	TupleDesc	fc_tupleDescriptor;
	int			fc_i;

	fc_tupleDescriptor = RelationGetDescr(fc_indexRelation);

	for (fc_i = 0; fc_i < fc_numatts; fc_i += 1)
		TupleDescAttr(fc_tupleDescriptor, fc_i)->attrelid = fc_indexoid;
}


/* ----------------------------------------------------------------
 *		追加属性元组
 * ----------------------------------------------------------------
 */
static void fc_AppendAttributeTuples(Relation fc_indexRelation, Datum *fc_attopts)
{
	Relation	pg_attribute;
	CatalogIndexState fc_indstate;
	TupleDesc	fc_indexTupDesc;

	/* 
	 * 打开属性关系及其索引
	 */
	pg_attribute = table_open(AttributeRelationId, RowExclusiveLock);

	fc_indstate = CatalogOpenIndexes(pg_attribute);

	/* 
	 * 将新索引的 tupdesc 中的数据插入 pg_attribute
	 */ 
	fc_indexTupDesc = RelationGetDescr(fc_indexRelation);

	InsertPgAttributeTuples(pg_attribute, fc_indexTupDesc, InvalidOid, fc_attopts, fc_indstate);

	CatalogCloseIndexes(fc_indstate);

	table_close(pg_attribute, RowExclusiveLock);
}

/* ----------------------------------------------------------------
 * 更新索引关系
 *
 * 在 pg_index 目录中构造并插入一个新条目
 * ----------------------------------------------------------------
 */
static void fc_UpdateIndexRelation(Oid fc_indexoid,
					Oid fc_heapoid,
					Oid fc_parentIndexId,
					IndexInfo *fc_indexInfo,
					Oid *fc_collationOids,
					Oid *fc_classOids,
					int16 *fc_coloptions,
					bool fc_primary,
					bool fc_isexclusion,
					bool fc_immediate,
					bool fc_isvalid,
					bool fc_isready)
{
	int2vector *fc_indkey;
	oidvector  *fc_indcollation;
	oidvector  *fc_indclass;
	int2vector *fc_indoption;
	Datum		fc_exprsDatum;
	Datum		fc_predDatum;
	Datum		fc_values[Natts_pg_index];
	bool		fc_nulls[Natts_pg_index];
	Relation	pg_index;
	HeapTuple	fc_tuple;
	int			fc_i;

	/*
	 * 将索引键、操作类和 indoption 信息复制到数组中（我们是否应让调用者一开始就这样传递它们？）
	 */
	fc_indkey = buildint2vector(NULL, fc_indexInfo->ii_NumIndexAttrs);
	for (fc_i = 0; fc_i < fc_indexInfo->ii_NumIndexAttrs; fc_i++)
		fc_indkey->values[fc_i] = fc_indexInfo->ii_IndexAttrNumbers[fc_i];
	fc_indcollation = buildoidvector(fc_collationOids, fc_indexInfo->ii_NumIndexKeyAttrs);
	fc_indclass = buildoidvector(fc_classOids, fc_indexInfo->ii_NumIndexKeyAttrs);
	fc_indoption = buildint2vector(fc_coloptions, fc_indexInfo->ii_NumIndexKeyAttrs);

	/*
	 * 将索引表达式（如果有的话）转换为文本数据
	 */
	if (fc_indexInfo->ii_Expressions != NIL)
	{
		char	   *fc_exprsString;

		fc_exprsString = nodeToString(fc_indexInfo->ii_Expressions);
		fc_exprsDatum = CStringGetTextDatum(fc_exprsString);
		pfree(fc_exprsString);
	}
	else
		fc_exprsDatum = (Datum) 0;

	/*
	 * 将索引谓词（如果有的话）转换为文本数据。注意我们将隐式-AND 格式转换为正常的显式-AND 以便存储。
	 */
	if (fc_indexInfo->ii_Predicate != NIL)
	{
		char	   *fc_predString;

		fc_predString = nodeToString(make_ands_explicit(fc_indexInfo->ii_Predicate));
		fc_predDatum = CStringGetTextDatum(fc_predString);
		pfree(fc_predString);
	}
	else
		fc_predDatum = (Datum) 0;


	/*
	 * 打开系统目录索引关系
	 */
	pg_index = table_open(IndexRelationId, RowExclusiveLock);

	/*
	 * 构建 pg_index 元组
	 */
	MemSet(fc_nulls, false, sizeof(fc_nulls));

	fc_values[Anum_pg_index_indexrelid - 1] = ObjectIdGetDatum(fc_indexoid);
	fc_values[Anum_pg_index_indrelid - 1] = ObjectIdGetDatum(fc_heapoid);
	fc_values[Anum_pg_index_indnatts - 1] = Int16GetDatum(fc_indexInfo->ii_NumIndexAttrs);
	fc_values[Anum_pg_index_indnkeyatts - 1] = Int16GetDatum(fc_indexInfo->ii_NumIndexKeyAttrs);
	fc_values[Anum_pg_index_indisunique - 1] = BoolGetDatum(fc_indexInfo->ii_Unique);
	fc_values[Anum_pg_index_indnullsnotdistinct - 1] = BoolGetDatum(fc_indexInfo->ii_NullsNotDistinct);
	fc_values[Anum_pg_index_indisprimary - 1] = BoolGetDatum(fc_primary);
	fc_values[Anum_pg_index_indisexclusion - 1] = BoolGetDatum(fc_isexclusion);
	fc_values[Anum_pg_index_indimmediate - 1] = BoolGetDatum(fc_immediate);
	fc_values[Anum_pg_index_indisclustered - 1] = BoolGetDatum(false);
	fc_values[Anum_pg_index_indisvalid - 1] = BoolGetDatum(fc_isvalid);
	fc_values[Anum_pg_index_indcheckxmin - 1] = BoolGetDatum(false);
	fc_values[Anum_pg_index_indisready - 1] = BoolGetDatum(fc_isready);
	fc_values[Anum_pg_index_indislive - 1] = BoolGetDatum(true);
	fc_values[Anum_pg_index_indisreplident - 1] = BoolGetDatum(false);
	fc_values[Anum_pg_index_indkey - 1] = PointerGetDatum(fc_indkey);
	fc_values[Anum_pg_index_indcollation - 1] = PointerGetDatum(fc_indcollation);
	fc_values[Anum_pg_index_indclass - 1] = PointerGetDatum(fc_indclass);
	fc_values[Anum_pg_index_indoption - 1] = PointerGetDatum(fc_indoption);
	fc_values[Anum_pg_index_indexprs - 1] = fc_exprsDatum;
	if (fc_exprsDatum == (Datum) 0)
		fc_nulls[Anum_pg_index_indexprs - 1] = true;
	fc_values[Anum_pg_index_indpred - 1] = fc_predDatum;
	if (fc_predDatum == (Datum) 0)
		fc_nulls[Anum_pg_index_indpred - 1] = true;

	fc_tuple = heap_form_tuple(RelationGetDescr(pg_index), fc_values, fc_nulls);

	/*
	 * 将元组插入到 pg_index 目录中
	 */
	CatalogTupleInsert(pg_index, fc_tuple);

	/*
	 * 关闭关系并释放元组
	 */
	table_close(pg_index, RowExclusiveLock);
	heap_freetuple(fc_tuple);
}


/*
 * index_create
 *
 * heapRelation: 要在其上构建索引的表（由调用者适当锁定）
 * indexRelationName: 其名称
 * indexRelationId: 通常传递 InvalidOid 让此例程为索引生成 OID。在引导期间，该值可能非零以指定预选的 OID。
 * parentIndexRelid: 如果创建索引分区，则为父索引的 OID；否则为 InvalidOid。
 * parentConstraintId: 如果在分区上创建约束，则为父级中约束的 OID；否则为 InvalidOid。
 * relFileNode: 通常，传递 InvalidOid 以获取新存储。可以非零以附加现有有效构建。
 * indexInfo: 执行器用于插入到索引中的相同信息
 * indexColNames: 用于索引的列名（字符列表 *）
 * accessMethodObjectId: 要使用的索引 AM 的 OID
 * tableSpaceId: 要使用的表空间的 OID
 * collationObjectId: 排序规则 OID 数组，每个索引列一个
 * classObjectId: 索引操作类 OID 数组，每个索引列一个
 * coloptions: 每个索引列的 indoption 设置数组
 * reloptions: AM 特定选项
 * flags: 可以包括这些位的任何组合的位掩码：
 *		INDEX_CREATE_IS_PRIMARY
 *			该索引是主键
 *		INDEX_CREATE_ADD_CONSTRAINT:
 *			还调用 index_constraint_create
 *		INDEX_CREATE_SKIP_BUILD:
 *			暂时跳过 index_build() 步骤；调用者必须稍后执行
 *			（通常通过 reindex_index()）
 *		INDEX_CREATE_CONCURRENT:
 *			不对写入者锁定表。该索引将被标记为“无效”，调用者必须采取额外措施
 *			修复它。
 *		INDEX_CREATE_IF_NOT_EXISTS:
 *			如果已经存在同名关系，则不引发错误。
 *		INDEX_CREATE_PARTITIONED:
 *			创建一个分区索引（表必须是分区的）
 * constr_flags: 传递给 index_constraint_create 的标志
 *		（仅在设置 INDEX_CREATE_ADD_CONSTRAINT 时）
 * allow_system_table_mods: 允许表为系统目录
 * is_internal: 如果为真，则为新索引的创建后钩子
 * constraintId: 如果不为 NULL，则接收创建的约束的 OID
 *
 * 返回创建的索引的 OID。
 */
Oid index_create(Relation fc_heapRelation,
			 const char *fc_indexRelationName,
			 Oid fc_indexRelationId,
			 Oid fc_parentIndexRelid,
			 Oid fc_parentConstraintId,
			 Oid fc_relFileNode,
			 IndexInfo *fc_indexInfo,
			 List *fc_indexColNames,
			 Oid fc_accessMethodObjectId,
			 Oid fc_tableSpaceId,
			 Oid *fc_collationObjectId,
			 Oid *fc_classObjectId,
			 int16 *fc_coloptions,
			 Datum fc_reloptions,
			 bits16 fc_flags,
			 bits16 fc_constr_flags,
			 bool fc_allow_system_table_mods,
			 bool fc_is_internal,
			 Oid *fc_constraintId)
{
	Oid			fc_heapRelationId = RelationGetRelid(fc_heapRelation);
	Relation	pg_class;
	Relation	fc_indexRelation;
	TupleDesc	fc_indexTupDesc;
	bool		fc_shared_relation;
	bool		fc_mapped_relation;
	bool		fc_is_exclusion;
	Oid			fc_namespaceId;
	int			fc_i;
	char		fc_relpersistence;
	bool		fc_isprimary = (fc_flags & INDEX_CREATE_IS_PRIMARY) != 0;
	bool		fc_invalid = (fc_flags & INDEX_CREATE_INVALID) != 0;
	bool		fc_concurrent = (fc_flags & INDEX_CREATE_CONCURRENT) != 0;
	bool		fc_partitioned = (fc_flags & INDEX_CREATE_PARTITIONED) != 0;
	char		fc_relkind;
	TransactionId fc_relfrozenxid;
	MultiXactId fc_relminmxid;
	bool		fc_create_storage = !OidIsValid(fc_relFileNode);

	/* 只有在请求约束时才能设置约束标志 */
	Assert((fc_constr_flags == 0) ||
		   ((fc_flags & INDEX_CREATE_ADD_CONSTRAINT) != 0));
	/* 分区索引绝不能单独“构建” */
	Assert(!fc_partitioned || (fc_flags & INDEX_CREATE_SKIP_BUILD));

	fc_relkind = fc_partitioned ? RELKIND_PARTITIONED_INDEX : RELKIND_INDEX;
	fc_is_exclusion = (fc_indexInfo->ii_ExclusionOps != NULL);

	pg_class = table_open(RelationRelationId, RowExclusiveLock);

	/*
	 * 索引将处于与其父表相同的命名空间中，并且仅当父表在不同数据库之间共享时，它才会被共享。同样，只有在父表使用时，它也将使用 relfilenode 映射；并且继承父表的 relpersistence。
	 */
	fc_namespaceId = RelationGetNamespace(fc_heapRelation);
	fc_shared_relation = fc_heapRelation->rd_rel->relisshared;
	fc_mapped_relation = RelationIsMapped(fc_heapRelation);
	fc_relpersistence = fc_heapRelation->rd_rel->relpersistence;

	/*
	 * 检查参数
	 */
	if (fc_indexInfo->ii_NumIndexAttrs < 1)
		elog(ERROR, "must index at least one column");

	if (!fc_allow_system_table_mods &&
		IsSystemRelation(fc_heapRelation) &&
		IsNormalProcessingMode())
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("user-defined indexes on system catalog tables are not supported")));

	/*
	 * Btree text_pattern_ops 使用 text_eq 作为相等运算符，只要排序规则是确定的，这很好；text_eq 然后简化为按位相等，因此在语义上与该操作类中的其他运算符和函数兼容。但是，对于非确定性排序规则，text_eq 可能产生与索引的实际行为（由操作类的比较函数决定）不兼容的结果。我们通过拒绝创建带有该操作类和非确定性排序规则的索引来防止此类问题。
	 *
	 * 同样适用于 varchar_pattern_ops 和 bpchar_pattern_ops。如果我们发现更多案例，我们可能决定创建一个真正的机制以标记与非确定性不兼容的操作类；但目前，这个小黑客就足够了。
	 *
	 * 另一个解决方案是使用特殊运算符，而不是 text_eq，作为相等操作类成员；但那是不可取的，因为那将阻止许多今天工作正常的查询使用索引。
	 */
	for (fc_i = 0; fc_i < fc_indexInfo->ii_NumIndexKeyAttrs; fc_i++)
	{
		Oid			fc_collation = fc_collationObjectId[fc_i];
		Oid			fc_opclass = fc_classObjectId[fc_i];

		if (fc_collation)
		{
			if ((fc_opclass == TEXT_BTREE_PATTERN_OPS_OID ||
				 fc_opclass == VARCHAR_BTREE_PATTERN_OPS_OID ||
				 fc_opclass == BPCHAR_BTREE_PATTERN_OPS_OID) &&
				!get_collation_isdeterministic(fc_collation))
			{
				HeapTuple	fc_classtup;

				fc_classtup = SearchSysCache1(CLAOID, ObjectIdGetDatum(fc_opclass));
				if (!HeapTupleIsValid(fc_classtup))
					elog(ERROR, "cache lookup failed for operator class %u", fc_opclass);
				ereport(ERROR,
						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
						 errmsg("nondeterministic collations are not supported for operator class \"%s\"",
								NameStr(((Form_pg_opclass) GETSTRUCT(fc_classtup))->opcname))));
				ReleaseSysCache(fc_classtup);
			}
		}
	}

	/*
	 * 在系统目录上并行构建索引是不安全的，因为我们倾向于在目录中提交前释放锁。
	 */
	if (fc_concurrent &&
		IsCatalogRelation(fc_heapRelation))
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("concurrent index creation on system catalog tables is not supported")));

	/*
	 * 此情况当前不支持。在 CREATE INDEX 的语法中无法请求它，但它可能在 REINDEX 时发生。
	 */
	if (fc_concurrent && fc_is_exclusion)
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("concurrent index creation for exclusion constraints is not supported")));

	
/*
	 * 我们不能在 initdb 后允许为共享关系建立索引（因为没有办法在其他数据库的 pg_class 中创建条目）。
	 */
	if (fc_shared_relation && !IsBootstrapProcessingMode())
		ereport(ERROR,
				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
				 errmsg("shared indexes cannot be created after initdb")));

	/*
	 * 共享关系也必须在 pg_global 中（最后检查）
	 */
	if (fc_shared_relation && fc_tableSpaceId != GLOBALTABLESPACE_OID)
		elog(ERROR, "shared relations must be placed in pg_global tablespace");

	/*
	 * 检查重复名称（既对索引，又对相关约束（如果有的话））。这种情况无论如何都会在相关目录的唯一索引上失败，但我们更倾向于给出更友好的错误信息。
	 */
	if (get_relname_relid(fc_indexRelationName, fc_namespaceId))
	{
		if ((fc_flags & INDEX_CREATE_IF_NOT_EXISTS) != 0)
		{
			ereport(NOTICE,
					(errcode(ERRCODE_DUPLICATE_TABLE),
					 errmsg("relation \"%s\" already exists, skipping",
							fc_indexRelationName)));
			table_close(pg_class, RowExclusiveLock);
			return InvalidOid;
		}

		ereport(ERROR,
				(errcode(ERRCODE_DUPLICATE_TABLE),
				 errmsg("relation \"%s\" already exists",
						fc_indexRelationName)));
	}

	if ((fc_flags & INDEX_CREATE_ADD_CONSTRAINT) != 0 &&
		ConstraintNameIsUsed(CONSTRAINT_RELATION, fc_heapRelationId,
							 fc_indexRelationName))
	{
		/*
		 * INDEX_CREATE_IF_NOT_EXISTS 在这里不适用，因为冲突的约束不是一个索引。
		 */
		ereport(ERROR,
				(errcode(ERRCODE_DUPLICATE_OBJECT),
				 errmsg("constraint \"%s\" for relation \"%s\" already exists",
						fc_indexRelationName, RelationGetRelationName(fc_heapRelation))));
	}

	/*
	 * 为索引元组构建元组描述符
	 */
	fc_indexTupDesc = fc_ConstructTupleDescriptor(fc_heapRelation,
											fc_indexInfo,
											fc_indexColNames,
											fc_accessMethodObjectId,
											fc_collationObjectId,
											fc_classObjectId);

	/*
	 * 为索引分配 OID，除非我们被告知使用什么。
	 *
	 * OID 也将是 relfilenode，因此请确保它与 pg_class OID 或现有物理文件不发生冲突。
	 */
	if (!OidIsValid(fc_indexRelationId))
	{
		/* 对 pg_class.oid 和 relfilenode 使用二进制升级覆盖 */
		if (IsBinaryUpgrade)
		{
			if (!OidIsValid(binary_upgrade_next_index_pg_class_oid))
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
						 errmsg("pg_class index OID value not set when in binary upgrade mode")));

			fc_indexRelationId = binary_upgrade_next_index_pg_class_oid;
			binary_upgrade_next_index_pg_class_oid = InvalidOid;

			/* 覆盖索引 relfilenode */
			if ((fc_relkind == RELKIND_INDEX) &&
				(!OidIsValid(binary_upgrade_next_index_pg_class_relfilenode)))
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
						 errmsg("index relfilenode value not set when in binary upgrade mode")));
			fc_relFileNode = binary_upgrade_next_index_pg_class_relfilenode;
			binary_upgrade_next_index_pg_class_relfilenode = InvalidOid;

			/*
			 * 注意，我们希望在二进制升级中 create_storage = true。我们在这里创建的存储稍后会被替换，但在此期间我们需要磁盘上有一些东西。
			 */
			Assert(fc_create_storage);
		}
		else
		{
			fc_indexRelationId =
				GetNewRelFileNode(fc_tableSpaceId, pg_class, fc_relpersistence);
		}
	}

	/*
	 * 创建索引关系的 relcache 条目，并在必要时创建物理磁盘文件。（如果我们进一步失败，smgr 有责任再次删除磁盘文件（如果有的话）。）
	 */
	fc_indexRelation = heap_create(fc_indexRelationName,
								fc_namespaceId,
								fc_tableSpaceId,
								fc_indexRelationId,
								fc_relFileNode,
								fc_accessMethodObjectId,
								fc_indexTupDesc,
								fc_relkind,
								fc_relpersistence,
								fc_shared_relation,
								fc_mapped_relation,
								fc_allow_system_table_mods,
								&fc_relfrozenxid,
								&fc_relminmxid,
								fc_create_storage);

	Assert(fc_relfrozenxid == InvalidTransactionId);
	Assert(fc_relminmxid == InvalidMultiXactId);
	Assert(fc_indexRelationId == RelationGetRelid(fc_indexRelation));

	/*
	 * 获取对其的独占锁。虽然其他事务在我们提交之前无法看到它，但这可以防止在 CLUSTER 等情况下出现死锁风险投诉。
	 */
	LockRelation(fc_indexRelation, AccessExclusiveLock);

	/*
	 * 填充索引的 pg_class 条目中未正确设置的字段。
	 *
	 * XXX 应该有更简洁的方法来创建目录中的索引
	 */
	fc_indexRelation->rd_rel->relowner = fc_heapRelation->rd_rel->relowner;
	fc_indexRelation->rd_rel->relam = fc_accessMethodObjectId;
	fc_indexRelation->rd_rel->relispartition = OidIsValid(fc_parentIndexRelid);

	/*
	 * 存储索引的 pg_class 条目
	 */
	InsertPgClassTuple(pg_class, fc_indexRelation,
					   RelationGetRelid(fc_indexRelation),
					   (Datum) 0,
					   fc_reloptions);

	/* 完成 pg_class */
	table_close(pg_class, RowExclusiveLock);

	/*
	 * 现在更新索引关系的元组描述符中所有属性元组形式的对象 ID
	 */
	fc_InitializeAttributeOids(fc_indexRelation,
							fc_indexInfo->ii_NumIndexAttrs,
							fc_indexRelationId);

	/*
	 * 为索引附加 ATTRIBUTE 元组
	 */
	fc_AppendAttributeTuples(fc_indexRelation, fc_indexInfo->ii_OpclassOptions);

	/* ----------------
	 *	  更新 pg_index
	 *	  (附加 INDEX 元组)
	 *
	 *	  请注意，这将存储“谓词”的表示。
	 *	  （或者，可以定义一个规则来维护谓词） --Nels, 92年2月
	 * ----------------
	 */
	fc_UpdateIndexRelation(fc_indexRelationId, fc_heapRelationId, fc_parentIndexRelid,
						fc_indexInfo,
						fc_collationObjectId, fc_classObjectId, fc_coloptions,
						fc_isprimary, fc_is_exclusion,
						(fc_constr_flags & INDEX_CONSTR_CREATE_DEFERRABLE) == 0,
						!fc_concurrent && !fc_invalid,
						!fc_concurrent);

	/*
	 * 注册索引的堆关系上的 relcache 失效，以维护其索引列表的一致性
	 */
	CacheInvalidateRelcache(fc_heapRelation);

	/* 如果需要，更新 pg_inherits 和父关系的 relhassubclass */
	if (OidIsValid(fc_parentIndexRelid))
	{
		StoreSingleInheritance(fc_indexRelationId, fc_parentIndexRelid, 1);
		LockRelationOid(fc_parentIndexRelid, ShareUpdateExclusiveLock);
		SetRelationHasSubclass(fc_parentIndexRelid, true);
	}

	/*
	 * 注册索引的约束和依赖关系。
	 *
	 * 如果索引来自 CONSTRAINT 子句，则构建 pg_constraint
	 * 条目。该索引将链接到约束，而约束又链接到表。如果不是 CONSTRAINT，
	 * 我们需要直接依赖于表。
	 *
	 * 我们不需要对命名空间的依赖关系，因为会通过我们的父表产生间接依赖。
	 *
	 * 在引导时，我们无法注册任何依赖关系，也不会尝试
	 * 创建约束。
	 */
	if (!IsBootstrapProcessingMode())
	{
		ObjectAddress fc_myself,
					fc_referenced;
		ObjectAddresses *fc_addrs;

		ObjectAddressSet(fc_myself, RelationRelationId, fc_indexRelationId);

		if ((fc_flags & INDEX_CREATE_ADD_CONSTRAINT) != 0)
		{
			char		fc_constraintType;
			ObjectAddress fc_localaddr;

			if (fc_isprimary)
				fc_constraintType = CONSTRAINT_PRIMARY;
			else if (fc_indexInfo->ii_Unique)
				fc_constraintType = CONSTRAINT_UNIQUE;
			else if (fc_is_exclusion)
				fc_constraintType = CONSTRAINT_EXCLUSION;
			else
			{
				elog(ERROR, "constraint must be PRIMARY, UNIQUE or EXCLUDE");
				fc_constraintType = 0; /* 保持编译器安静 */
			}

			fc_localaddr = index_constraint_create(fc_heapRelation,
												fc_indexRelationId,
												fc_parentConstraintId,
												fc_indexInfo,
												fc_indexRelationName,
												fc_constraintType,
												fc_constr_flags,
												fc_allow_system_table_mods,
												fc_is_internal);
			if (fc_constraintId)
				*fc_constraintId = fc_localaddr.objectId;
		}
		else
		{
			bool		fc_have_simple_col = false;

			fc_addrs = new_object_addresses();

			/* 在简单引用的列上创建自动依赖关系 */
			for (fc_i = 0; fc_i < fc_indexInfo->ii_NumIndexAttrs; fc_i++)
			{
				if (fc_indexInfo->ii_IndexAttrNumbers[fc_i] != 0)
				{
					ObjectAddressSubSet(fc_referenced, RelationRelationId,
										fc_heapRelationId,
										fc_indexInfo->ii_IndexAttrNumbers[fc_i]);
					add_exact_object_address(&fc_referenced, fc_addrs);
					fc_have_simple_col = true;
				}
			}

			/*
			 * 如果没有简单引用的列，则对整个表给索引一个
			 * 自动依赖关系。在大多数情况下，这将是冗余的，
			 * 但如果索引表达式和谓词中没有 Vars 或仅有行 Vars，则可能不是。
			 */
			if (!fc_have_simple_col)
			{
				ObjectAddressSet(fc_referenced, RelationRelationId,
								 fc_heapRelationId);
				add_exact_object_address(&fc_referenced, fc_addrs);
			}

			record_object_address_dependencies(&fc_myself, fc_addrs, DEPENDENCY_AUTO);
			free_object_addresses(fc_addrs);
		}

		/*
		 * 如果这是一个索引分区，则在父索引和表上创建分区依赖关系。
		 * （注意：这些必须是*另外的*，而不是替代所有其他依赖关系。
		 * 否则，在 DETACH PARTITION 后，我们会缺少一些依赖关系。）
		 */
		if (OidIsValid(fc_parentIndexRelid))
		{
			ObjectAddressSet(fc_referenced, RelationRelationId, fc_parentIndexRelid);
			recordDependencyOn(&fc_myself, &fc_referenced, DEPENDENCY_PARTITION_PRI);

			ObjectAddressSet(fc_referenced, RelationRelationId, fc_heapRelationId);
			recordDependencyOn(&fc_myself, &fc_referenced, DEPENDENCY_PARTITION_SEC);
		}

		/* 正常依赖关系的占位符 */
		fc_addrs = new_object_addresses();

		/* 存储对排序规则的依赖关系 */

		/* 默认排序规则是固定的，因此不必记录它 */
		for (fc_i = 0; fc_i < fc_indexInfo->ii_NumIndexKeyAttrs; fc_i++)
		{
			if (OidIsValid(fc_collationObjectId[fc_i]) &&
				fc_collationObjectId[fc_i] != DEFAULT_COLLATION_OID)
			{
				ObjectAddressSet(fc_referenced, CollationRelationId,
								 fc_collationObjectId[fc_i]);
				add_exact_object_address(&fc_referenced, fc_addrs);
			}
		}

		/* 存储对运算符类的依赖关系 */
		for (fc_i = 0; fc_i < fc_indexInfo->ii_NumIndexKeyAttrs; fc_i++)
		{
			ObjectAddressSet(fc_referenced, OperatorClassRelationId, fc_classObjectId[fc_i]);
			add_exact_object_address(&fc_referenced, fc_addrs);
		}

		record_object_address_dependencies(&fc_myself, fc_addrs, DEPENDENCY_NORMAL);
		free_object_addresses(fc_addrs);

		/* 存储对索引表达式中提到的任何内容的依赖关系 */
		if (fc_indexInfo->ii_Expressions)
		{
			recordDependencyOnSingleRelExpr(&fc_myself,
											(Node *) fc_indexInfo->ii_Expressions,
											fc_heapRelationId,
											DEPENDENCY_NORMAL,
											DEPENDENCY_AUTO, false);
		}

		/* 存储对谓词中提到的任何内容的依赖关系 */
		if (fc_indexInfo->ii_Predicate)
		{
			recordDependencyOnSingleRelExpr(&fc_myself,
											(Node *) fc_indexInfo->ii_Predicate,
											fc_heapRelationId,
											DEPENDENCY_NORMAL,
											DEPENDENCY_AUTO, false);
		}
	}
	else
	{
		/* 引导模式 - 断言我们未被要求支持约束 */
		Assert((fc_flags & INDEX_CREATE_ADD_CONSTRAINT) == 0);
	}

	/* 新索引的创建后钩子 */
	InvokeObjectPostCreateHookArg(RelationRelationId,
								  fc_indexRelationId, 0, fc_is_internal);

	/*
	 * 前进命令计数器，以便我们可以看到新进入的
	 * 目录元组。
	 */
	CommandCounterIncrement();

	/*
	 * 在引导模式下，我们必须用目录中的信息填充索引策略结构。
	 * 如果我们不是在引导，那么 relcache 条目已经通过
	 * CommandCounterIncrement 期间的 sinval 更新重建。
	 */
	if (IsBootstrapProcessingMode())
		RelationInitIndexAccessInfo(fc_indexRelation);
	else
		Assert(fc_indexRelation->rd_indexcxt != NULL);

	fc_indexRelation->rd_index->indnkeyatts = fc_indexInfo->ii_NumIndexKeyAttrs;

	/* 验证 opclass 特定选项 */
	if (fc_indexInfo->ii_OpclassOptions)
		for (fc_i = 0; fc_i < fc_indexInfo->ii_NumIndexKeyAttrs; fc_i++)
			(void) index_opclass_options(fc_indexRelation, fc_i + 1,
										 fc_indexInfo->ii_OpclassOptions[fc_i],
										 true);

	/*
	 * 如果这是引导（initdb）时间，那么我们实际上还不填充
	 * 索引。我们将稍后创建更多的索引和类，因此
	 * 我们将在引导完成之前延迟填充它们。
	 * 类似地，如果调用者指定跳过构建，则填充索引在稍后延迟
	 * （ALTER TABLE 在某些情况下可以节省工作）。
	 * 否则，我们调用构造索引的 AM 例程。
	 */
	if (IsBootstrapProcessingMode())
	{
		index_register(fc_heapRelationId, fc_indexRelationId, fc_indexInfo);
	}
	else if ((fc_flags & INDEX_CREATE_SKIP_BUILD) != 0)
	{
		/*
		 * 调用者负责稍后填充索引。然而，
		 * 我们最好确保堆关系被正确标记为
		 * 拥有一个索引。
		 */
		fc_index_update_stats(fc_heapRelation,
						   true,
						   -1.0);
		/* 使上述更新可见 */
		CommandCounterIncrement();
	}
	else
	{
		index_build(fc_heapRelation, fc_indexRelation, fc_indexInfo, false, true);
	}

	/*
	 * 关闭索引；但是我们保留在事物结束前获取的锁定。
	 * 关闭堆是调用者的责任。
	 */
	index_close(fc_indexRelation, NoLock);

	return fc_indexRelationId;
}

/*
 * index_concurrently_create_copy
 *
 * 根据调用者提供的定义并发创建索引。
 * 索引将插入到目录中，并需要稍后构建。
 * 这在并发重新索引处理期间调用。
 *
 * "tablespaceOid" 是用于该索引的表空间。
 */
Oid index_concurrently_create_copy(Relation fc_heapRelation, Oid fc_oldIndexId,
							   Oid fc_tablespaceOid, const char *fc_newName)
{
	Relation	fc_indexRelation;
	IndexInfo  *fc_oldInfo,
			   *fc_newInfo;
	Oid			fc_newIndexId = InvalidOid;
	HeapTuple	fc_indexTuple,
				fc_classTuple;
	Datum		fc_indclassDatum,
				fc_colOptionDatum,
				fc_optionDatum;
	oidvector  *fc_indclass;
	int2vector *fc_indcoloptions;
	bool		fc_isnull;
	List	   *fc_indexColNames = NIL;
	List	   *fc_indexExprs = NIL;
	List	   *fc_indexPreds = NIL;

	fc_indexRelation = index_open(fc_oldIndexId, RowExclusiveLock);

	/* 新索引需要一些来自旧索引的信息 */
	fc_oldInfo = BuildIndexInfo(fc_indexRelation);

	/*
	 * 不支持具有排斥约束的索引的并发构建。
	 */
	if (fc_oldInfo->ii_ExclusionOps != NULL)
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("concurrent index creation for exclusion constraints is not supported")));

	/* 从索引信息中获取类和列选项 ID 的数组 */
	fc_indexTuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(fc_oldIndexId));
	if (!HeapTupleIsValid(fc_indexTuple))
		elog(ERROR, "cache lookup failed for index %u", fc_oldIndexId);
	fc_indclassDatum = SysCacheGetAttr(INDEXRELID, fc_indexTuple,
									Anum_pg_index_indclass, &fc_isnull);
	Assert(!fc_isnull);
	fc_indclass = (oidvector *) DatumGetPointer(fc_indclassDatum);

	fc_colOptionDatum = SysCacheGetAttr(INDEXRELID, fc_indexTuple,
									 Anum_pg_index_indoption, &fc_isnull);
	Assert(!fc_isnull);
	fc_indcoloptions = (int2vector *) DatumGetPointer(fc_colOptionDatum);

	/* 如果有，获取索引的选项 */
	fc_classTuple = SearchSysCache1(RELOID, fc_oldIndexId);
	if (!HeapTupleIsValid(fc_classTuple))
		elog(ERROR, "cache lookup failed for relation %u", fc_oldIndexId);
	fc_optionDatum = SysCacheGetAttr(RELOID, fc_classTuple,
								  Anum_pg_class_reloptions, &fc_isnull);

	/*
	 * 直接从目录中获取表达式和谓词列表。
	 * 这不能依赖于旧索引的 IndexInfo 中的信息，
	 * 因为这些信息已被平整化以供规划器使用。
	 */
	if (fc_oldInfo->ii_Expressions != NIL)
	{
		Datum		fc_exprDatum;
		char	   *fc_exprString;

		fc_exprDatum = SysCacheGetAttr(INDEXRELID, fc_indexTuple,
									Anum_pg_index_indexprs, &fc_isnull);
		Assert(!fc_isnull);
		fc_exprString = TextDatumGetCString(fc_exprDatum);
		fc_indexExprs = (List *) stringToNode(fc_exprString);
		pfree(fc_exprString);
	}
	if (fc_oldInfo->ii_Predicate != NIL)
	{
		Datum		fc_predDatum;
		char	   *fc_predString;

		fc_predDatum = SysCacheGetAttr(INDEXRELID, fc_indexTuple,
									Anum_pg_index_indpred, &fc_isnull);
		Assert(!fc_isnull);
		fc_predString = TextDatumGetCString(fc_predDatum);
		fc_indexPreds = (List *) stringToNode(fc_predString);

		/* 也转换为隐式 AND 格式 */
		fc_indexPreds = make_ands_implicit((Expr *) fc_indexPreds);
		pfree(fc_predString);
	}

	/*
	 * 为新索引构建索引信息。注意，不支持具有排除约束的索引重建，因此没有必要填充所有的 ii_Exclusion* 字段。
	 */
	fc_newInfo = makeIndexInfo(fc_oldInfo->ii_NumIndexAttrs,
							fc_oldInfo->ii_NumIndexKeyAttrs,
							fc_oldInfo->ii_Am,
							fc_indexExprs,
							fc_indexPreds,
							fc_oldInfo->ii_Unique,
							fc_oldInfo->ii_NullsNotDistinct,
							false,	/* 尚未准备好进行插入 */
							true);

	/*
	 * 提取新索引信息的列名称和列编号列表。所有这些信息将用于索引的创建。
	 */
	for (int fc_i = 0; fc_i < fc_oldInfo->ii_NumIndexAttrs; fc_i++)
	{
		TupleDesc	fc_indexTupDesc = RelationGetDescr(fc_indexRelation);
		Form_pg_attribute fc_att = TupleDescAttr(fc_indexTupDesc, fc_i);

		fc_indexColNames = lappend(fc_indexColNames, NameStr(fc_att->attname));
		fc_newInfo->ii_IndexAttrNumbers[fc_i] = fc_oldInfo->ii_IndexAttrNumbers[fc_i];
	}

	/* 提取每个属性的 opclass 参数（如果有） */
	if (fc_oldInfo->ii_OpclassOptions != NULL)
	{
		fc_newInfo->ii_OpclassOptions = palloc0(sizeof(Datum) *
											 fc_newInfo->ii_NumIndexAttrs);
		for (int fc_i = 0; fc_i < fc_newInfo->ii_NumIndexAttrs; fc_i++)
			fc_newInfo->ii_OpclassOptions[fc_i] = get_attoptions(fc_oldIndexId, fc_i + 1);
	}

	/*
	 * 现在创建新的索引。
	 *
	 * 对于分区索引，我们稍后调整分区依赖，以确保始终保持一致的状态。这就是为什么 parentIndexRelid
	 * 在这里没有设置的原因。
	 */
	fc_newIndexId = index_create(fc_heapRelation,
							  fc_newName,
							  InvalidOid,	/* indexRelationId */
							  InvalidOid,	/* parentIndexRelid */
							  InvalidOid,	/* parentConstraintId */
							  InvalidOid,	/* relFileNode */
							  fc_newInfo,
							  fc_indexColNames,
							  fc_indexRelation->rd_rel->relam,
							  fc_tablespaceOid,
							  fc_indexRelation->rd_indcollation,
							  fc_indclass->values,
							  fc_indcoloptions->values,
							  fc_optionDatum,
							  INDEX_CREATE_SKIP_BUILD | INDEX_CREATE_CONCURRENT,
							  0,
							  true, /* 允许表作为系统目录？ */
							  false,	/* is_internal? */
							  NULL);

	/* 关闭使用的关系并清理 */
	index_close(fc_indexRelation, NoLock);
	ReleaseSysCache(fc_indexTuple);
	ReleaseSysCache(fc_classTuple);

	return fc_newIndexId;
}

/*
 * index_concurrently_build
 *
 * 为并发操作构建索引。在执行此操作时，将获取低级锁，以防止仅更改架构，但这些锁需要一直保留到执行此操作的事务结束。
 * 'indexOid' 指的是作为先前处理的一部分已经创建的索引关系 OID，'heapOid' 指其父堆关系。
 */
void index_concurrently_build(Oid fc_heapRelationId,
						 Oid fc_indexRelationId)
{
	Relation	fc_heapRel;
	Oid			fc_save_userid;
	int			fc_save_sec_context;
	int			fc_save_nestlevel;
	Relation	fc_indexRelation;
	IndexInfo  *fc_indexInfo;

	/* 这最好确保快照处于活动状态 */
	Assert(ActiveSnapshotSet());

	/* 打开并锁定父堆关系 */
	fc_heapRel = table_open(fc_heapRelationId, ShareUpdateExclusiveLock);

	/*
	 * 切换到表所有者的用户ID，以便任何索引函数以该用户身份运行。
	 * 同时锁定安全性受限的操作，并安排将 GUC 变量更改限制在此命令中。
	 */
	GetUserIdAndSecContext(&fc_save_userid, &fc_save_sec_context);
	SetUserIdAndSecContext(fc_heapRel->rd_rel->relowner,
						   fc_save_sec_context | SECURITY_RESTRICTED_OPERATION);
	fc_save_nestlevel = NewGUCNestLevel();

	fc_indexRelation = index_open(fc_indexRelationId, RowExclusiveLock);

	/*
	 * 我们需要重新构建 IndexInfo 结构，因为它在创建此并发索引的事务提交中丢失了
	 * 在目录级别。
	 */
	fc_indexInfo = BuildIndexInfo(fc_indexRelation);
	Assert(!fc_indexInfo->ii_ReadyForInserts);
	fc_indexInfo->ii_Concurrent = true;
	fc_indexInfo->ii_BrokenHotChain = false;

	/* 现在构建索引 */
	index_build(fc_heapRel, fc_indexRelation, fc_indexInfo, false, true);

	/* 回滚索引函数执行的任何 GUC 更改 */
	AtEOXact_GUC(false, fc_save_nestlevel);

	/* 恢复 userid 和安全上下文 */
	SetUserIdAndSecContext(fc_save_userid, fc_save_sec_context);

	/* 关闭这两个关系，但保留锁 */
	table_close(fc_heapRel, NoLock);
	index_close(fc_indexRelation, NoLock);

	/*
	 * 更新 pg_index 行以标记索引为准备好进行插入。一旦我们提交此事务，任何打开该表的新事务必须
	 * 在插入和非 HOT 更新中向索引插入新条目。
	 */
	index_set_state_flags(fc_indexRelationId, INDEX_CREATE_SET_READY);
}

/*
 * index_concurrently_swap
 *
 * 将旧索引的名称、依赖项和约束交换到新索引，同时将旧索引标记为无效并将新索引标记为有效。
 */
void index_concurrently_swap(Oid fc_newIndexId, Oid fc_oldIndexId, const char *fc_oldName)
{
	Relation	pg_class,
				pg_index,
				pg_constraint,
				pg_trigger;
	Relation	fc_oldClassRel,
				fc_newClassRel;
	HeapTuple	fc_oldClassTuple,
				fc_newClassTuple;
	Form_pg_class fc_oldClassForm,
				fc_newClassForm;
	HeapTuple	fc_oldIndexTuple,
				fc_newIndexTuple;
	Form_pg_index fc_oldIndexForm,
				fc_newIndexForm;
	bool		fc_isPartition;
	Oid			fc_indexConstraintOid;
	List	   *fc_constraintOids = NIL;
	ListCell   *fc_lc;

	/*
	 * 在交换之前对旧索引和新索引进行必要的锁定。
	 */
	fc_oldClassRel = relation_open(fc_oldIndexId, ShareUpdateExclusiveLock);
	fc_newClassRel = relation_open(fc_newIndexId, ShareUpdateExclusiveLock);

	/* 现在交换这些索引的名称和依赖项 */
	pg_class = table_open(RelationRelationId, RowExclusiveLock);

	fc_oldClassTuple = SearchSysCacheCopy1(RELOID,
										ObjectIdGetDatum(fc_oldIndexId));
	if (!HeapTupleIsValid(fc_oldClassTuple))
		elog(ERROR, "could not find tuple for relation %u", fc_oldIndexId);
	fc_newClassTuple = SearchSysCacheCopy1(RELOID,
										ObjectIdGetDatum(fc_newIndexId));
	if (!HeapTupleIsValid(fc_newClassTuple))
		elog(ERROR, "could not find tuple for relation %u", fc_newIndexId);

	fc_oldClassForm = (Form_pg_class) GETSTRUCT(fc_oldClassTuple);
	fc_newClassForm = (Form_pg_class) GETSTRUCT(fc_newClassTuple);

	/* 交换名称 */
	namestrcpy(&fc_newClassForm->relname, NameStr(fc_oldClassForm->relname));
	namestrcpy(&fc_oldClassForm->relname, fc_oldName);

	/* 交换分区标志以正确跟踪继承 */
	fc_isPartition = fc_newClassForm->relispartition;
	fc_newClassForm->relispartition = fc_oldClassForm->relispartition;
	fc_oldClassForm->relispartition = fc_isPartition;

	CatalogTupleUpdate(pg_class, &fc_oldClassTuple->t_self, fc_oldClassTuple);
	CatalogTupleUpdate(pg_class, &fc_newClassTuple->t_self, fc_newClassTuple);

	heap_freetuple(fc_oldClassTuple);
	heap_freetuple(fc_newClassTuple);

	/* 现在交换索引信息 */
	pg_index = table_open(IndexRelationId, RowExclusiveLock);

	fc_oldIndexTuple = SearchSysCacheCopy1(INDEXRELID,
										ObjectIdGetDatum(fc_oldIndexId));
	if (!HeapTupleIsValid(fc_oldIndexTuple))
		elog(ERROR, "could not find tuple for relation %u", fc_oldIndexId);
	fc_newIndexTuple = SearchSysCacheCopy1(INDEXRELID,
										ObjectIdGetDatum(fc_newIndexId));
	if (!HeapTupleIsValid(fc_newIndexTuple))
		elog(ERROR, "could not find tuple for relation %u", fc_newIndexId);

	fc_oldIndexForm = (Form_pg_index) GETSTRUCT(fc_oldIndexTuple);
	fc_newIndexForm = (Form_pg_index) GETSTRUCT(fc_newIndexTuple);

	/*
	 * 从旧索引复制约束标志。这是安全的，因为旧索引保证了唯一性。
	 */
	fc_newIndexForm->indisprimary = fc_oldIndexForm->indisprimary;
	fc_oldIndexForm->indisprimary = false;
	fc_newIndexForm->indisexclusion = fc_oldIndexForm->indisexclusion;
	fc_oldIndexForm->indisexclusion = false;
	fc_newIndexForm->indimmediate = fc_oldIndexForm->indimmediate;
	fc_oldIndexForm->indimmediate = true;

	/* 在新索引中保留 indisreplident */
	fc_newIndexForm->indisreplident = fc_oldIndexForm->indisreplident;

	/* 在新索引中保留 indisclustered */
	fc_newIndexForm->indisclustered = fc_oldIndexForm->indisclustered;

	/*
	 * 将新索引标记为有效，并将旧索引标记为无效，类似于 index_set_state_flags() 的作用。
	 */
	fc_newIndexForm->indisvalid = true;
	fc_oldIndexForm->indisvalid = false;
	fc_oldIndexForm->indisclustered = false;
	fc_oldIndexForm->indisreplident = false;

	CatalogTupleUpdate(pg_index, &fc_oldIndexTuple->t_self, fc_oldIndexTuple);
	CatalogTupleUpdate(pg_index, &fc_newIndexTuple->t_self, fc_newIndexTuple);

	heap_freetuple(fc_oldIndexTuple);
	heap_freetuple(fc_newIndexTuple);

	/*
	 * 将约束和触发器移动到新索引
	 */

	fc_constraintOids = get_index_ref_constraints(fc_oldIndexId);

	fc_indexConstraintOid = get_index_constraint(fc_oldIndexId);

	if (OidIsValid(fc_indexConstraintOid))
		fc_constraintOids = lappend_oid(fc_constraintOids, fc_indexConstraintOid);

	pg_constraint = table_open(ConstraintRelationId, RowExclusiveLock);
	pg_trigger = table_open(TriggerRelationId, RowExclusiveLock);

	foreach(fc_lc, fc_constraintOids)
	{
		HeapTuple	fc_constraintTuple,
					fc_triggerTuple;
		Form_pg_constraint fc_conForm;
		ScanKeyData fc_key[1];
		SysScanDesc fc_scan;
		Oid			fc_constraintOid = lfirst_oid(fc_lc);

		/* 将约束从旧索引移动到新索引 */
		fc_constraintTuple = SearchSysCacheCopy1(CONSTROID,
											  ObjectIdGetDatum(fc_constraintOid));
		if (!HeapTupleIsValid(fc_constraintTuple))
			elog(ERROR, "could not find tuple for constraint %u", fc_constraintOid);

		fc_conForm = ((Form_pg_constraint) GETSTRUCT(fc_constraintTuple));

		if (fc_conForm->conindid == fc_oldIndexId)
		{
			fc_conForm->conindid = fc_newIndexId;

			CatalogTupleUpdate(pg_constraint, &fc_constraintTuple->t_self, fc_constraintTuple);
		}

		heap_freetuple(fc_constraintTuple);

		/* 搜索触发记录 */
		ScanKeyInit(&fc_key[0],
					Anum_pg_trigger_tgconstraint,
					BTEqualStrategyNumber, F_OIDEQ,
					ObjectIdGetDatum(fc_constraintOid));

		fc_scan = systable_beginscan(pg_trigger, TriggerConstraintIndexId, true,
								  NULL, 1, fc_key);

		while (HeapTupleIsValid((fc_triggerTuple = systable_getnext(fc_scan))))
		{
			Form_pg_trigger fc_tgForm = (Form_pg_trigger) GETSTRUCT(fc_triggerTuple);

			if (fc_tgForm->tgconstrindid != fc_oldIndexId)
				continue;

			/* 制作一个可修改的副本 */
			fc_triggerTuple = heap_copytuple(fc_triggerTuple);
			fc_tgForm = (Form_pg_trigger) GETSTRUCT(fc_triggerTuple);

			fc_tgForm->tgconstrindid = fc_newIndexId;

			CatalogTupleUpdate(pg_trigger, &fc_triggerTuple->t_self, fc_triggerTuple);

			heap_freetuple(fc_triggerTuple);
		}

		systable_endscan(fc_scan);
	}

	/*
	 * 如果有注释则移动
	 */
	{
		Relation	fc_description;
		ScanKeyData fc_skey[3];
		SysScanDesc fc_sd;
		HeapTuple	fc_tuple;
		Datum		fc_values[Natts_pg_description] = {0};
		bool		fc_nulls[Natts_pg_description] = {0};
		bool		fc_replaces[Natts_pg_description] = {0};

		fc_values[Anum_pg_description_objoid - 1] = ObjectIdGetDatum(fc_newIndexId);
		fc_replaces[Anum_pg_description_objoid - 1] = true;

		ScanKeyInit(&fc_skey[0],
					Anum_pg_description_objoid,
					BTEqualStrategyNumber, F_OIDEQ,
					ObjectIdGetDatum(fc_oldIndexId));
		ScanKeyInit(&fc_skey[1],
					Anum_pg_description_classoid,
					BTEqualStrategyNumber, F_OIDEQ,
					ObjectIdGetDatum(RelationRelationId));
		ScanKeyInit(&fc_skey[2],
					Anum_pg_description_objsubid,
					BTEqualStrategyNumber, F_INT4EQ,
					Int32GetDatum(0));

		fc_description = table_open(DescriptionRelationId, RowExclusiveLock);

		fc_sd = systable_beginscan(fc_description, DescriptionObjIndexId, true,
								NULL, 3, fc_skey);

		while ((fc_tuple = systable_getnext(fc_sd)) != NULL)
		{
			fc_tuple = heap_modify_tuple(fc_tuple, RelationGetDescr(fc_description),
									  fc_values, fc_nulls, fc_replaces);
			CatalogTupleUpdate(fc_description, &fc_tuple->t_self, fc_tuple);

			break;				/* 假设只能有一个匹配 */
		}

		systable_endscan(fc_sd);
		table_close(fc_description, NoLock);
	}

	/*
	 * 与父索引交换继承关系
	 */
	if (get_rel_relispartition(fc_oldIndexId))
	{
		List	   *fc_ancestors = get_partition_ancestors(fc_oldIndexId);
		Oid			fc_parentIndexRelid = linitial_oid(fc_ancestors);

		DeleteInheritsTuple(fc_oldIndexId, fc_parentIndexRelid, false, NULL);
		StoreSingleInheritance(fc_newIndexId, fc_parentIndexRelid, 1);

		list_free(fc_ancestors);
	}

	/*
	 * 将旧索引的所有依赖项交换到新索引上，并
	 * 反之亦然。请注意，调用 CommandCounterIncrement() 会导致
	 * pg_depend 中的重复条目，因此不应执行此操作。
	 */
	changeDependenciesOf(RelationRelationId, fc_newIndexId, fc_oldIndexId);
	changeDependenciesOn(RelationRelationId, fc_newIndexId, fc_oldIndexId);

	changeDependenciesOf(RelationRelationId, fc_oldIndexId, fc_newIndexId);
	changeDependenciesOn(RelationRelationId, fc_oldIndexId, fc_newIndexId);

	/* 将统计数据从旧索引复制到新索引 */
	pgstat_copy_relation_stats(fc_newClassRel, fc_oldClassRel);

	/* 将 pg_statistic 的数据从旧索引复制到新索引 */
	CopyStatistics(fc_oldIndexId, fc_newIndexId);

	/* 复制每个索引属性的 pg_attribute.attstattarget */
	{
		HeapTuple	fc_attrTuple;
		Relation	pg_attribute;
		SysScanDesc fc_scan;
		ScanKeyData fc_key[1];

		pg_attribute = table_open(AttributeRelationId, RowExclusiveLock);
		ScanKeyInit(&fc_key[0],
					Anum_pg_attribute_attrelid,
					BTEqualStrategyNumber, F_OIDEQ,
					ObjectIdGetDatum(fc_newIndexId));
		fc_scan = systable_beginscan(pg_attribute, AttributeRelidNumIndexId,
								  true, NULL, 1, fc_key);

		while (HeapTupleIsValid((fc_attrTuple = systable_getnext(fc_scan))))
		{
			Form_pg_attribute fc_att = (Form_pg_attribute) GETSTRUCT(fc_attrTuple);
			Datum		fc_repl_val[Natts_pg_attribute];
			bool		fc_repl_null[Natts_pg_attribute];
			bool		fc_repl_repl[Natts_pg_attribute];
			int			fc_attstattarget;
			HeapTuple	fc_newTuple;

			/* 忽略已删除的列 */
			if (fc_att->attisdropped)
				continue;

			/*
			 * 从旧索引获取 attstattarget 并刷新新值。
			 */
			fc_attstattarget = get_attstattarget(fc_oldIndexId, fc_att->attnum);

			/* 如果两者匹配，则无需刷新 */
			if (fc_attstattarget == fc_att->attstattarget)
				continue;

			memset(fc_repl_val, 0, sizeof(fc_repl_val));
			memset(fc_repl_null, false, sizeof(fc_repl_null));
			memset(fc_repl_repl, false, sizeof(fc_repl_repl));

			fc_repl_repl[Anum_pg_attribute_attstattarget - 1] = true;
			fc_repl_val[Anum_pg_attribute_attstattarget - 1] = Int32GetDatum(fc_attstattarget);

			fc_newTuple = heap_modify_tuple(fc_attrTuple,
										 RelationGetDescr(pg_attribute),
										 fc_repl_val, fc_repl_null, fc_repl_repl);
			CatalogTupleUpdate(pg_attribute, &fc_newTuple->t_self, fc_newTuple);

			heap_freetuple(fc_newTuple);
		}

		systable_endscan(fc_scan);
		table_close(pg_attribute, RowExclusiveLock);
	}

	/* 关闭关系 */
	table_close(pg_class, RowExclusiveLock);
	table_close(pg_index, RowExclusiveLock);
	table_close(pg_constraint, RowExclusiveLock);
	table_close(pg_trigger, RowExclusiveLock);

	/* 之前获得的锁将在事务结束之前不会释放 */
	relation_close(fc_oldClassRel, NoLock);
	relation_close(fc_newClassRel, NoLock);
}

/*
 * index_concurrently_set_dead
 *
 * 在实际删除索引之前执行 DROP INDEX CONCURRENTLY 或 REINDEX
 * CONCURRENTLY 的最后无效化阶段。在调用此函数后，所有后端都
 * 会将该索引视为已死亡。在此处获得的低级锁将保留到调用此函数的
 * 事务结束。
 */
void index_concurrently_set_dead(Oid fc_heapId, Oid fc_indexId)
{
	Relation	fc_userHeapRelation;
	Relation	fc_userIndexRelation;

	/*
	 * 不再会在此索引上获取谓词锁，我们即将停止对该索引的插入操作，
	 * 这可能会与现有的谓词锁发生冲突，因此现在是将它们移动到堆
	 * 关系的时机。
	 */
	fc_userHeapRelation = table_open(fc_heapId, ShareUpdateExclusiveLock);
	fc_userIndexRelation = index_open(fc_indexId, ShareUpdateExclusiveLock);
	TransferPredicateLocksToHeapRelation(fc_userIndexRelation);

	/*
	 * 现在我们可以确认没有人使用该索引进行查询；他们可能只是
	 * 打开它以更新。因此现在我们可以取消 indisready 和
	 * indislive，然后等待没有人再使用它。
	 */
	index_set_state_flags(fc_indexId, INDEX_DROP_SET_DEAD);

	/*
	 * 使表的 relcache 无效，这样在此提交之后，所有会话都将刷新
	 * 表的索引列表。仅仅忘记索引的 relcache 条目是不够的。
	 */
	CacheInvalidateRelcache(fc_userHeapRelation);

	/*
	 * 再次关闭关系，尽管仍然持有会话锁。
	 */
	table_close(fc_userHeapRelation, NoLock);
	index_close(fc_userIndexRelation, NoLock);
}

/*
 * index_constraint_create
 *
 * 设置与索引关联的约束。返回新约束的地址。
 *
 * heapRelation：拥有索引的表（调用者必须对其进行适当锁定）
 * indexRelationId：索引的 OID
 * parentConstraintId：如果约束在分区上，则为父级中约束的 OID。
 * indexInfo：执行器用于插入索引的相同信息
 * constraintName：表示的名称（通常应与索引名称匹配）
 * constraintType：CONSTRAINT_PRIMARY、CONSTRAINT_UNIQUE 或
 *		CONSTRAINT_EXCLUSION之一
 * flags：可以包含以下任意组合的位掩码：
 *		INDEX_CONSTR_CREATE_MARK_AS_PRIMARY：索引是 PRIMARY KEY
 *		INDEX_CONSTR_CREATE_DEFERRABLE：约束是 DEFERRABLE
 *		INDEX_CONSTR_CREATE_INIT_DEFERRED：约束是 INITIALLY DEFERRED
 *		INDEX_CONSTR_CREATE_UPDATE_INDEX：更新 pg_index 行
 *		INDEX_CONSTR_CREATE_REMOVE_OLD_DEPS：删除索引在表列上的现有依赖项
 * allow_system_table_mods：允许表作为系统目录
 * is_internal：索引因内部过程构建
 */
ObjectAddress index_constraint_create(Relation fc_heapRelation,
						Oid fc_indexRelationId,
						Oid fc_parentConstraintId,
						IndexInfo *fc_indexInfo,
						const char *fc_constraintName,
						char fc_constraintType,
						bits16 fc_constr_flags,
						bool fc_allow_system_table_mods,
						bool fc_is_internal)
{
	Oid			fc_namespaceId = RelationGetNamespace(fc_heapRelation);
	ObjectAddress fc_myself,
				fc_idxaddr;
	Oid			fc_conOid;
	bool		fc_deferrable;
	bool		fc_initdeferred;
	bool		fc_mark_as_primary;
	bool		fc_islocal;
	bool		fc_noinherit;
	int			fc_inhcount;

	fc_deferrable = (fc_constr_flags & INDEX_CONSTR_CREATE_DEFERRABLE) != 0;
	fc_initdeferred = (fc_constr_flags & INDEX_CONSTR_CREATE_INIT_DEFERRED) != 0;
	fc_mark_as_primary = (fc_constr_flags & INDEX_CONSTR_CREATE_MARK_AS_PRIMARY) != 0;

	/* 在引导时不支持约束创建 */
	Assert(!IsBootstrapProcessingMode());

	/* 强制执行系统表限制 */
	if (!fc_allow_system_table_mods &&
		IsSystemRelation(fc_heapRelation) &&
		IsNormalProcessingMode())
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("user-defined indexes on system catalog tables are not supported")));

	/* 主键/唯一约束不应具有任何表达式 */
	if (fc_indexInfo->ii_Expressions &&
		fc_constraintType != CONSTRAINT_EXCLUSION)
		elog(ERROR, "constraints cannot have index expressions");

	/*
	 * 如果我们为现有索引制造约束，则需要
	 * 消除现有的自动依赖关系（索引_create() 本应创建的那些， 
	 * 而不是调用此函数的）。
	 *
	 * 注意：如果索引具有任何表达式或谓词，这段代码可能不会做正确的事情，
	 * 但我们绝不会将这样一个索引转变为 UNIQUE 或 PRIMARY KEY 约束。
	 */
	if (fc_constr_flags & INDEX_CONSTR_CREATE_REMOVE_OLD_DEPS)
		deleteDependencyRecordsForClass(RelationRelationId, fc_indexRelationId,
										RelationRelationId, DEPENDENCY_AUTO);

	if (OidIsValid(fc_parentConstraintId))
	{
		fc_islocal = false;
		fc_inhcount = 1;
		fc_noinherit = false;
	}
	else
	{
		fc_islocal = true;
		fc_inhcount = 0;
		fc_noinherit = true;
	}

	/*
	 * 构造一个 pg_constraint 条目。
	 */
	fc_conOid = CreateConstraintEntry(fc_constraintName,
								   fc_namespaceId,
								   fc_constraintType,
								   fc_deferrable,
								   fc_initdeferred,
								   true,
								   fc_parentConstraintId,
								   RelationGetRelid(fc_heapRelation),
								   fc_indexInfo->ii_IndexAttrNumbers,
								   fc_indexInfo->ii_NumIndexKeyAttrs,
								   fc_indexInfo->ii_NumIndexAttrs,
								   InvalidOid,	/* 没有域 */
								   fc_indexRelationId, /* 索引 OID */
								   InvalidOid,	/* 没有外键 */
								   NULL,
								   NULL,
								   NULL,
								   NULL,
								   0,
								   ' ',
								   ' ',
								   NULL,
								   0,
								   ' ',
								   fc_indexInfo->ii_ExclusionOps,
								   NULL,	/* 没有检查约束 */
								   NULL,
								   fc_islocal,
								   fc_inhcount,
								   fc_noinherit,
								   fc_is_internal);

	/*
	 * 将索引注册为对约束的内部依赖。
	 *
	 * 注意，约束对表有依赖，因此我们不需要（也不想要）任何
	 * 从索引到表的直接依赖关系。
	 */
	ObjectAddressSet(fc_myself, ConstraintRelationId, fc_conOid);
	ObjectAddressSet(fc_idxaddr, RelationRelationId, fc_indexRelationId);
	recordDependencyOn(&fc_idxaddr, &fc_myself, DEPENDENCY_INTERNAL);

	/*
	 * 此外，如果这是对分区的约束，则在父约束以及表上赋予
	 * 分区类型的依赖关系。
	 */
	if (OidIsValid(fc_parentConstraintId))
	{
		ObjectAddress fc_referenced;

		ObjectAddressSet(fc_referenced, ConstraintRelationId, fc_parentConstraintId);
		recordDependencyOn(&fc_myself, &fc_referenced, DEPENDENCY_PARTITION_PRI);
		ObjectAddressSet(fc_referenced, RelationRelationId,
						 RelationGetRelid(fc_heapRelation));
		recordDependencyOn(&fc_myself, &fc_referenced, DEPENDENCY_PARTITION_SEC);
	}

	/*
	 * 如果约束是可延迟的，则创建延迟唯一
	 * 检查触发器。(触发器将通过 CreateTrigger 被赋予对
	 * 约束的内部依赖。)
	 */
	if (fc_deferrable)
	{
		CreateTrigStmt *fc_trigger = makeNode(CreateTrigStmt);

		fc_trigger->replace = false;
		fc_trigger->isconstraint = true;
		fc_trigger->trigname = (fc_constraintType == CONSTRAINT_PRIMARY) ?
			"PK_ConstraintTrigger" :
			"Unique_ConstraintTrigger";
		fc_trigger->relation = NULL;
		fc_trigger->funcname = SystemFuncName("unique_key_recheck");
		fc_trigger->args = NIL;
		fc_trigger->row = true;
		fc_trigger->timing = TRIGGER_TYPE_AFTER;
		fc_trigger->events = TRIGGER_TYPE_INSERT | TRIGGER_TYPE_UPDATE;
		fc_trigger->columns = NIL;
		fc_trigger->whenClause = NULL;
		fc_trigger->transitionRels = NIL;
		fc_trigger->deferrable = true;
		fc_trigger->initdeferred = fc_initdeferred;
		fc_trigger->constrrel = NULL;

		(void) CreateTrigger(fc_trigger, NULL, RelationGetRelid(fc_heapRelation),
							 InvalidOid, fc_conOid, fc_indexRelationId, InvalidOid,
							 InvalidOid, NULL, true, false);
	}

	/*
	 * 如果需要，在 pg_index 中将索引标记为主键和/或延迟。
	 *
	 * 注意：在将现有索引转变为约束时，调用者必须对表进行锁定，
	 * 以防止并发的表更新；否则，有并发读取者可能会完全错过看到该
	 * 索引的风险。
	 */
	if ((fc_constr_flags & INDEX_CONSTR_CREATE_UPDATE_INDEX) &&
		(fc_mark_as_primary || fc_deferrable))
	{
		Relation	pg_index;
		HeapTuple	fc_indexTuple;
		Form_pg_index fc_indexForm;
		bool		fc_dirty = false;
		bool		fc_marked_as_primary = false;

		pg_index = table_open(IndexRelationId, RowExclusiveLock);

		fc_indexTuple = SearchSysCacheCopy1(INDEXRELID,
										 ObjectIdGetDatum(fc_indexRelationId));
		if (!HeapTupleIsValid(fc_indexTuple))
			elog(ERROR, "cache lookup failed for index %u", fc_indexRelationId);
		fc_indexForm = (Form_pg_index) GETSTRUCT(fc_indexTuple);

		if (fc_mark_as_primary && !fc_indexForm->indisprimary)
		{
			fc_indexForm->indisprimary = true;
			fc_dirty = true;
			fc_marked_as_primary = true;
		}

		if (fc_deferrable && fc_indexForm->indimmediate)
		{
			fc_indexForm->indimmediate = false;
			fc_dirty = true;
		}

		if (fc_dirty)
		{
			CatalogTupleUpdate(pg_index, &fc_indexTuple->t_self, fc_indexTuple);

			
			if (fc_marked_as_primary)
				CacheInvalidateRelcache(fc_heapRelation);

			InvokeObjectPostAlterHookArg(IndexRelationId, fc_indexRelationId, 0,
										 InvalidOid, fc_is_internal);
		}

		heap_freetuple(fc_indexTuple);
		table_close(pg_index, RowExclusiveLock);
	}

	return fc_myself;
}

/*
 *		index_drop
 *
 * 注意：该例程现在只能通过 performDeletion() 调用，
 * 否则相关依赖项将无法清理。
 *
 * 如果 concurrent 为真，则执行 DROP INDEX CONCURRENTLY。
 * 如果 concurrent 为假但 concurrent_lock_mode 为真，则执行正常的 DROP INDEX
 * 但为 CONCURRENTLY 处理获取一个锁。这部分用于 REINDEX
 * CONCURRENTLY。
 */
void index_drop(Oid fc_indexId, bool fc_concurrent, bool fc_concurrent_lock_mode)
{
	Oid			fc_heapId;
	Relation	fc_userHeapRelation;
	Relation	fc_userIndexRelation;
	Relation	fc_indexRelation;
	HeapTuple	fc_tuple;
	bool		fc_hasexprs;
	LockRelId	fc_heaprelid,
				fc_indexrelid;
	LOCKTAG		fc_heaplocktag;
	LOCKMODE	fc_lockmode;

	/*
	 * 临时关系使用非并发的 DROP。其他后端不能
	 * 访问临时关系，因此获取更强的锁没有坏处（参见 RemoveRelations 中的评论），并且非并发的 DROP
	 * 更高效。
	 */
	Assert(get_rel_persistence(fc_indexId) != RELPERSISTENCE_TEMP ||
		   (!fc_concurrent && !fc_concurrent_lock_mode));

	/*
	 * 为了安全地删除索引，我们必须在其父表上获取独占锁。
	 * 单独的索引上的独占锁是不够的，因为
	 * 其他后端可能即将执行父表的查询。
	 * 如果它依赖于之前缓存的索引 OID 列表，则可能会
	 * 尝试访问刚刚删除的索引。因此，我们必须获取一个
	 * 足够强的表锁，以防止所有查询在提交之前继续，并发送一个共享缓存失效通知
	 *，使它们更新其索引列表。
	 *
	 * 在并发情况下，我们通过分多个步骤禁用索引使用来避免此要求，并等待
	 * 任何可能正在使用索引的事务，因此我们不需要在父表上获取独占锁。相反
	 * 我们获取 ShareUpdateExclusiveLock，以确保两个会话不会在同一索引上
	 * 同时执行 CREATE/DROP INDEX CONCURRENTLY。（我们下面会在确认没有其他人
	 * 在使用它时获取 AccessExclusiveLock。）
	 */
	fc_heapId = IndexGetRelation(fc_indexId, false);
	fc_lockmode = (fc_concurrent || fc_concurrent_lock_mode) ? ShareUpdateExclusiveLock : AccessExclusiveLock;
	fc_userHeapRelation = table_open(fc_heapId, fc_lockmode);
	fc_userIndexRelation = index_open(fc_indexId, fc_lockmode);

	/*
	 * 我们可能仍然有开放的查询在我们的会话中使用它，以上锁定无法防止，因此需要明确测试。
	 */
	CheckTableNotInUse(fc_userIndexRelation, "DROP INDEX");

	/*
	 * 并发删除索引的过程或多或少是
	 * 并发创建索引的逆过程。
	 *
	 * 首先我们取消设置 indisvalid，以便之后开始的查询不再使用
	 * 索引来回答查询。我们必须保持 indisready = true，以便
	 * 仍在扫描索引的事务可以继续查看
	 * 有效的索引内容。例如，如果它们使用的是 READ COMMITTED
	 * 模式，另一个事务进行更改并提交时，它们需要在索引中看到这些新的元组。
	 *
	 * 当所有可能使用该索引进行查询的事务结束后，
	 * 我们可以取消设置 indisready 和 indislive，然后等待
	 * 直到没有人再触及它。（注意：我们需要 indislive，因为
	 * 该状态必须与 CREATE INDEX
	 * CONCURRENTLY 过程中初始状态不同，该状态在 indisready 和 indisvalid
	 * 为假时 indislive 为真。因为在该状态下，事务必须检查
	 * 索引以进行 HOT 安全决策，而在此状态下我们不希望它们
	 * 访问索引。）
	 *
	 * 由于索引上的所有谓词锁即将失效，我们
	 * 必须将它们提升为堆上的谓词锁。在
	 * 非并发情况下，我们可以现在就做到这一点。在并发情况下
	 * 情况就复杂一些。只有在索引上没有正在进行的索引扫描并且
	 * 不再能开始的情况下，我们才能移动谓词锁，以便不再能在索引上创建新的谓词锁。
	 * 此外，它们必须在堆插入停止维护索引之前移动，否则
	 * 在堆关系锁定以检测基于堆元组插入的冲突之前，可能会错过与索引间隙上谓词锁的冲突。
	 */
	if (fc_concurrent)
	{
		/*
		 * 我们必须提交事务，以使第一次 pg_index
		 * 状态更新对其他会话可见。如果 DROP 机制
		 * 已经执行了其他任何操作（其他对象的移除，
		 * pg_depend 条目等），则提交将使这些操作
		 * 变为永久，这将使我们在以下序列中如果失败时
		 * 遇到不一致的目录状态。由于 DROP INDEX
		 * CONCURRENTLY 限制为仅删除一个没有
		 * 依赖关系的索引，我们应该在什么都没有做之前到达这里——
		 * 但让我们检查一下以确保。我们可以通过检查
		 * 当前事务没有执行任何事务更新来验证
		 * 没有分配 XID。
		 */
		if (GetTopTransactionIdIfAny() != InvalidTransactionId)
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("DROP INDEX CONCURRENTLY must be first action in transaction")));

		/*
		 * 通过更新其 pg_index 条目来标记索引无效
		 */
		index_set_state_flags(fc_indexId, INDEX_DROP_CLEAR_VALID);

		/*
		 * 使表的relcache无效，以便在此提交后
		 * 所有会话将刷新任何可能引用该索引的缓存计划。
		 */
		CacheInvalidateRelcache(fc_userHeapRelation);

		/* 保存 lockrelid 和 locktag 以供下面使用，然后关闭但保留锁 */
		fc_heaprelid = fc_userHeapRelation->rd_lockInfo.lockRelId;
		SET_LOCKTAG_RELATION(fc_heaplocktag, fc_heaprelid.dbId, fc_heaprelid.relId);
		fc_indexrelid = fc_userIndexRelation->rd_lockInfo.lockRelId;

		table_close(fc_userHeapRelation, NoLock);
		index_close(fc_userIndexRelation, NoLock);

		
/*
		 * 我们必须提交当前事务，以便无效更新对其他事务可见；然后开始另一个事务。
		 * 请注意，在提交时，任何先前构建的数据结构都会丢失。我们保留的唯一数据是关系 ID。
		 *
		 * 在提交之前，获取表的会话级锁，以确保在我们完成之前既不能删除表也不能删除索引。
		 * 即使其他人正在等待访问，这也不能阻塞，因为我们在事务中已经获得了相同的锁。
		 */
		LockRelationIdForSession(&fc_heaprelid, ShareUpdateExclusiveLock);
		LockRelationIdForSession(&fc_indexrelid, ShareUpdateExclusiveLock);

		PopActiveSnapshot();
		CommitTransactionCommand();
		StartTransactionCommand();

		/*
		 * 现在我们必须等待直到没有正在运行的事务使用索引进行查询。
		 * 在这里使用 AccessExclusiveLock 来检查是否有正在持有任何类型锁的运行事务在表上。
		 * 请注意，我们不必担心在此之后打开表进行读取的事务；当它们打开关系时，会将索引视为无效。
		 *
		 * 注意：我们在这里使用实际的锁获取，而不是仅仅检查 ProcArray 并进入休眠，
		 * 是因为如果其中一个事务在尝试获取我们表的排他锁时被阻塞，则可能会发生死锁。
		 * 锁代码将检测到死锁并正确错误。
		 *
		 * 注意：我们在这里无条件通过 WaitForLockers() 报告进度，即使它只会在
		 * 由 REINDEX CONCURRENTLY 调用时使用，而不是在由 DROP INDEX CONCURRENTLY 调用时使用。
		 */
		WaitForLockers(fc_heaplocktag, AccessExclusiveLock, true);

		/* 完成索引的无效化并将其标记为无效 */
		index_concurrently_set_dead(fc_heapId, fc_indexId);

		/*
		 * 再次提交事务，以使 pg_index 更新对其他会话可见。
		 */
		CommitTransactionCommand();
		StartTransactionCommand();

		/*
		 * 等待直到每个看到旧索引状态的事务都已完成。
		 * 关于进度报告，请参见上文。
		 */
		WaitForLockers(fc_heaplocktag, AccessExclusiveLock, true);

		/*
		 * 重新打开关系，以便允许我们完成操作。
		 *
		 * 此时，应该没有任何人访问索引，但让我们在物理删除之前获取索引的 AccessExclusiveLock，
		 * 以免留下任何不确定因素。
		 */
		fc_userHeapRelation = table_open(fc_heapId, ShareUpdateExclusiveLock);
		fc_userIndexRelation = index_open(fc_indexId, AccessExclusiveLock);
	}
	else
	{
		/* 不是并发的，因此只需转移谓词锁即可 */
		TransferPredicateLocksToHeapRelation(fc_userIndexRelation);
	}

	/*
	 * 安排物理删除文件（如果有的话）
	 */
	if (RELKIND_HAS_STORAGE(fc_userIndexRelation->rd_rel->relkind))
		RelationDropStorage(fc_userIndexRelation);

	/* 确保如果事务提交，则统计信息被删除 */
	pgstat_drop_relation(fc_userIndexRelation);

	/*
	 * 关闭并刷新索引的 relcache 条目，以确保 relcache 在我们删除目录条目时不会试图重建它。
	 * 不过我们还是保持锁定。
	 */
	index_close(fc_userIndexRelation, NoLock);

	RelationForgetRelation(fc_indexId);

	/*
	 * 修复 INDEX 关系，并检查表达式索引
	 */
	fc_indexRelation = table_open(IndexRelationId, RowExclusiveLock);

	fc_tuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(fc_indexId));
	if (!HeapTupleIsValid(fc_tuple))
		elog(ERROR, "cache lookup failed for index %u", fc_indexId);

	fc_hasexprs = !heap_attisnull(fc_tuple, Anum_pg_index_indexprs,
							   RelationGetDescr(fc_indexRelation));

	CatalogTupleDelete(fc_indexRelation, &fc_tuple->t_self);

	ReleaseSysCache(fc_tuple);
	table_close(fc_indexRelation, RowExclusiveLock);

	/*
	 * 如果它有任何表达式列，我们可能已经存储了关于它们的统计信息。
	 */
	if (fc_hasexprs)
		RemoveStatistics(fc_indexId, 0);

	/*
	 * 修复 ATTRIBUTE 关系
	 */
	DeleteAttributeTuples(fc_indexId);

	/*
	 * 修复 RELATION 关系
	 */
	DeleteRelationTuple(fc_indexId);

	/*
	 * 修复 INHERITS 关系
	 */
	DeleteInheritsTuple(fc_indexId, InvalidOid, false, NULL);

	/*
	 * 我们目前过于懒惰，无法尝试计算 relhasindex 的新正确值（下一个 VACUUM 将在必要时修复它）。
	 * 因此，无需更新拥有关系的 pg_class 元组。
	 * 但我们必须向拥有关系发送一个共享缓存失效通知，以确保其他后端更新其索引的 relcache 列表。
	 * （在并发情况下，这会是多余的，但无害。）
	 */
	CacheInvalidateRelcache(fc_userHeapRelation);

	/*
	 * 关闭拥有的关系，但保持锁定
	 */
	table_close(fc_userHeapRelation, NoLock);

	/*
	 * 在离开之前释放会话锁。
	 */
	if (fc_concurrent)
	{
		UnlockRelationIdForSession(&fc_heaprelid, ShareUpdateExclusiveLock);
		UnlockRelationIdForSession(&fc_indexrelid, ShareUpdateExclusiveLock);
	}
}

/* ----------------------------------------------------------------
 *						index_build 支持
 * ----------------------------------------------------------------
 */

/* ----------------
 *		BuildIndexInfo
 *			为打开的索引构建 IndexInfo 记录
 *
 * IndexInfo 存储构建个别索引元组所需的关于索引的信息，这些信息在 index_build() 和后续插入中使用。
 * 通常情况下，我们为一个索引仅为每个命令构建一次 IndexInfo，然后使用它来处理（潜在的）多个元组。
 * ----------------
 */
IndexInfo * BuildIndexInfo(Relation fc_index)
{
	IndexInfo  *fc_ii;
	Form_pg_index fc_indexStruct = fc_index->rd_index;
	int			fc_i;
	int			fc_numAtts;

	/* 检查键的数量，并将属性编号复制到 IndexInfo 中 */
	fc_numAtts = fc_indexStruct->indnatts;
	if (fc_numAtts < 1 || fc_numAtts > INDEX_MAX_KEYS)
		elog(ERROR, "invalid indnatts %d for index %u",
			 fc_numAtts, RelationGetRelid(fc_index));

	/*
	 * 创建节点，获取任何需要的表达式以用于表达式索引和索引谓词（如果有）。
	 */
	fc_ii = makeIndexInfo(fc_indexStruct->indnatts,
					   fc_indexStruct->indnkeyatts,
					   fc_index->rd_rel->relam,
					   RelationGetIndexExpressions(fc_index),
					   RelationGetIndexPredicate(fc_index),
					   fc_indexStruct->indisunique,
					   fc_indexStruct->indnullsnotdistinct,
					   fc_indexStruct->indisready,
					   false);

	
/* 填充属性编号 */
	for (fc_i = 0; fc_i < fc_numAtts; fc_i++)
		fc_ii->ii_IndexAttrNumbers[fc_i] = fc_indexStruct->indkey.values[fc_i];

	/* 如果有的话，获取排除约束信息 */
	if (fc_indexStruct->indisexclusion)
	{
		RelationGetExclusionInfo(fc_index,
								 &fc_ii->ii_ExclusionOps,
								 &fc_ii->ii_ExclusionProcs,
								 &fc_ii->ii_ExclusionStrats);
	}

	fc_ii->ii_OpclassOptions = RelationGetIndexRawAttOptions(fc_index);

	return fc_ii;
}

/* ----------------
 *		BuildDummyIndexInfo
 *			为一个打开的索引构建一个虚拟的 IndexInfo 记录
 *
 * 这与实际的 BuildIndexInfo 不同，因为它不会运行任何用户定义的代码，
 * 这些代码可能存在于索引表达式或谓词中。
 * 我们返回的不是实际的索引表达式，而是具有正确类型/类型修饰符/排序规则的 null 常量。
 * 谓词和排除子句会被忽略。这对于截断索引的目的来说是足够的，
 * 因为我们不需要实际评估表达式或谓词；
 * 对于数据可能完成的唯一操作是构建描述索引行类型的 tupdesc。
 * ----------------
 */
IndexInfo * BuildDummyIndexInfo(Relation fc_index)
{
	IndexInfo  *fc_ii;
	Form_pg_index fc_indexStruct = fc_index->rd_index;
	int			fc_i;
	int			fc_numAtts;

	/* 检查键的数量，并将属性编号复制到 IndexInfo 中 */
	fc_numAtts = fc_indexStruct->indnatts;
	if (fc_numAtts < 1 || fc_numAtts > INDEX_MAX_KEYS)
		elog(ERROR, "invalid indnatts %d for index %u",
			 fc_numAtts, RelationGetRelid(fc_index));

	/*
	 * 创建节点，使用虚拟索引表达式，并假装没有谓词。
	 */
	fc_ii = makeIndexInfo(fc_indexStruct->indnatts,
					   fc_indexStruct->indnkeyatts,
					   fc_index->rd_rel->relam,
					   RelationGetDummyIndexExpressions(fc_index),
					   NIL,
					   fc_indexStruct->indisunique,
					   fc_indexStruct->indnullsnotdistinct,
					   fc_indexStruct->indisready,
					   false);

	
/* 填充属性编号 */
	for (fc_i = 0; fc_i < fc_numAtts; fc_i++)
		fc_ii->ii_IndexAttrNumbers[fc_i] = fc_indexStruct->indkey.values[fc_i];

	/* 我们忽略排除约束（如果有的话） */

	return fc_ii;
}

/*
 * CompareIndexInfo
 *		返回两个索引（在不同表中）的属性是否指示它们具有“相同”的定义。
 *
 * 注意：单独传递排序规则和操作族是一种权宜之计。
 * 将它们添加到 IndexInfo 可能会导致在这里和其他地方更好的代码。
 *
 * 使用 build_attrmap_by_name(index2, index1) 来构建 attmap。
 */
bool CompareIndexInfo(IndexInfo *fc_info1, IndexInfo *fc_info2,
				 Oid *fc_collations1, Oid *fc_collations2,
				 Oid *fc_opfamilies1, Oid *fc_opfamilies2,
				 AttrMap *fc_attmap)
{
	int			fc_i;

	if (fc_info1->ii_Unique != fc_info2->ii_Unique)
		return false;

	if (fc_info1->ii_NullsNotDistinct != fc_info2->ii_NullsNotDistinct)
		return false;

	/* 只有在它们具有相同的访问方法时，索引才是等价的 */
	if (fc_info1->ii_Am != fc_info2->ii_Am)
		return false;

	/* 和相同数量的属性 */
	if (fc_info1->ii_NumIndexAttrs != fc_info2->ii_NumIndexAttrs)
		return false;

	/* 和相同数量的关键属性 */
	if (fc_info1->ii_NumIndexKeyAttrs != fc_info2->ii_NumIndexKeyAttrs)
		return false;

	/*
	 * 并且通过属性映射匹配列（实际属性编号可能不同！）
	 * 请注意，这检查了作为表达式的索引列是否出现在相同的位置。
	 * 接下来我们将比较表达式本身。
	 */
	for (fc_i = 0; fc_i < fc_info1->ii_NumIndexAttrs; fc_i++)
	{
		if (fc_attmap->maplen < fc_info2->ii_IndexAttrNumbers[fc_i])
			elog(ERROR, "incorrect attribute map");

		/* 暂时忽略表达式（但检查它们的排序规则/操作族） */
		if (!(fc_info1->ii_IndexAttrNumbers[fc_i] == InvalidAttrNumber &&
			  fc_info2->ii_IndexAttrNumbers[fc_i] == InvalidAttrNumber))
		{
			/* 如果只有一个索引在此列中有表达式，则失败 */
			if (fc_info1->ii_IndexAttrNumbers[fc_i] == InvalidAttrNumber ||
				fc_info2->ii_IndexAttrNumbers[fc_i] == InvalidAttrNumber)
				return false;

			/* 两者都是列，因此在映射后检查匹配 */
			if (fc_attmap->attnums[fc_info2->ii_IndexAttrNumbers[fc_i] - 1] !=
				fc_info1->ii_IndexAttrNumbers[fc_i])
				return false;
		}

		/* 包含列不支持排序规则和操作族 */
		if (fc_i >= fc_info1->ii_NumIndexKeyAttrs)
			continue;

		if (fc_collations1[fc_i] != fc_collations2[fc_i])
			return false;
		if (fc_opfamilies1[fc_i] != fc_opfamilies2[fc_i])
			return false;
	}

	/*
	 * 对于表达式索引：要么都是表达式索引，要么都不是；
	 * 如果是，则确保表达式匹配。
	 */
	if ((fc_info1->ii_Expressions != NIL) != (fc_info2->ii_Expressions != NIL))
		return false;
	if (fc_info1->ii_Expressions != NIL)
	{
		bool		fc_found_whole_row;
		Node	   *fc_mapped;

		fc_mapped = map_variable_attnos((Node *) fc_info2->ii_Expressions,
									 1, 0, fc_attmap,
									 InvalidOid, &fc_found_whole_row);
		if (fc_found_whole_row)
		{
			/*
			 * 我们可以在这里抛出错误，但似乎超出了此例程的范围。
			 */
			return false;
		}

		if (!equal(fc_info1->ii_Expressions, fc_mapped))
			return false;
	}

	/* 如果存在，部分索引谓词必须是完全相同的 */
	if ((fc_info1->ii_Predicate == NULL) != (fc_info2->ii_Predicate == NULL))
		return false;
	if (fc_info1->ii_Predicate != NULL)
	{
		bool		fc_found_whole_row;
		Node	   *fc_mapped;

		fc_mapped = map_variable_attnos((Node *) fc_info2->ii_Predicate,
									 1, 0, fc_attmap,
									 InvalidOid, &fc_found_whole_row);
		if (fc_found_whole_row)
		{
			/*
			 * 我们可以在这里抛出错误，但似乎超出了此例程的范围。
			 */
			return false;
		}
		if (!equal(fc_info1->ii_Predicate, fc_mapped))
			return false;
	}

	/* 当前不支持比较排除索引。 */
	if (fc_info1->ii_ExclusionOps != NULL || fc_info2->ii_ExclusionOps != NULL)
		return false;

	return true;
}

/* ----------------
 *		BuildSpeculativeIndexInfo
 *			向 IndexInfo 记录添加额外状态
 *
 * 对于唯一索引，我们通常不想在 IndexInfo 中添加信息以检查唯一性，
 * 因为 B-Tree AM 直接处理此事。
 * 但是，在推测插入的情况下，需要额外的支持。
 *
 * 在这里而不是 BuildIndexInfo() 中执行此处理，以避免在常见的非推测情况下产生开销。
 * ----------------
 */
void BuildSpeculativeIndexInfo(Relation fc_index, IndexInfo *fc_ii)
{
	int			fc_indnkeyatts;
	int			fc_i;

	fc_indnkeyatts = IndexRelationGetNumberOfKeyAttributes(fc_index);

	/*
	 * 获取以检查唯一索引的信息
	 */
	Assert(fc_ii->ii_Unique);

	if (fc_index->rd_rel->relam != BTREE_AM_OID)
		elog(ERROR, "unexpected non-btree speculative unique index");

	fc_ii->ii_UniqueOps = (Oid *) palloc(sizeof(Oid) * fc_indnkeyatts);
	fc_ii->ii_UniqueProcs = (Oid *) palloc(sizeof(Oid) * fc_indnkeyatts);
	fc_ii->ii_UniqueStrats = (uint16 *) palloc(sizeof(uint16) * fc_indnkeyatts);

	/*
	 * 我们必须查找运算符的策略编号。这提供了一个交叉检查，确保运算符
	 * 与索引匹配。
	 */
	/* 我们也需要函数 OID 和策略编号 */
	for (fc_i = 0; fc_i < fc_indnkeyatts; fc_i++)
	{
		fc_ii->ii_UniqueStrats[fc_i] = BTEqualStrategyNumber;
		fc_ii->ii_UniqueOps[fc_i] =
			get_opfamily_member(fc_index->rd_opfamily[fc_i],
								fc_index->rd_opcintype[fc_i],
								fc_index->rd_opcintype[fc_i],
								fc_ii->ii_UniqueStrats[fc_i]);
		if (!OidIsValid(fc_ii->ii_UniqueOps[fc_i]))
			elog(ERROR, "missing operator %d(%u,%u) in opfamily %u",
				 fc_ii->ii_UniqueStrats[fc_i], fc_index->rd_opcintype[fc_i],
				 fc_index->rd_opcintype[fc_i], fc_index->rd_opfamily[fc_i]);
		fc_ii->ii_UniqueProcs[fc_i] = get_opcode(fc_ii->ii_UniqueOps[fc_i]);
	}
}

/* ----------------
 *		FormIndexDatum
 *		 为新索引元组构造 values[] 和 isnull[] 数组。
 *
 *	indexInfo		有关索引的信息
 *	slot			我们必须为之准备索引条目的堆元组
 *	estate			用于评估任何索引表达式的执行器状态
 *	values			索引 Datums 数组（输出区域）
 *	isnull			is-null 指示器数组（输出区域）
 *
 * 当没有索引表达式时， estate 可以为空。否则，它必须提供，并且
 * 每个元组表达式上下文的 ecxt_scantuple 插槽必须指向传入的堆元组。
 *
 * 请注意，我们在这里并没有实际调用 index_form_tuple()；
 * 我们只是准备它的输入数组 values[] 和 isnull[]。
 * 这是因为索引 AM 可能希望在存储之前修改数据。
 * ----------------
 */
void FormIndexDatum(IndexInfo *fc_indexInfo,
			   TupleTableSlot *fc_slot,
			   EState *fc_estate,
			   Datum *fc_values,
			   bool *fc_isnull)
{
	ListCell   *fc_indexpr_item;
	int			fc_i;

	if (fc_indexInfo->ii_Expressions != NIL &&
		fc_indexInfo->ii_ExpressionsState == NIL)
	{
		/* 第一次通过时，设置表达式评估状态 */
		fc_indexInfo->ii_ExpressionsState =
			ExecPrepareExprList(fc_indexInfo->ii_Expressions, fc_estate);
		/* 检查调用者是否已正确设置上下文 */
		Assert(GetPerTupleExprContext(fc_estate)->ecxt_scantuple == fc_slot);
	}
	fc_indexpr_item = list_head(fc_indexInfo->ii_ExpressionsState);

	for (fc_i = 0; fc_i < fc_indexInfo->ii_NumIndexAttrs; fc_i++)
	{
		int			fc_keycol = fc_indexInfo->ii_IndexAttrNumbers[fc_i];
		Datum		fc_iDatum;
		bool		fc_isNull;

		if (fc_keycol < 0)
			fc_iDatum = slot_getsysattr(fc_slot, fc_keycol, &fc_isNull);
		else if (fc_keycol != 0)
		{
			/*
			 * 普通索引列；直接从堆元组中获取我们需要的值。
			 */
			fc_iDatum = slot_getattr(fc_slot, fc_keycol, &fc_isNull);
		}
		else
		{
			/*
			 * 索引表达式 --- 需要对其进行评估。
			 */
			if (fc_indexpr_item == NULL)
				elog(ERROR, "wrong number of index expressions");
			fc_iDatum = ExecEvalExprSwitchContext((ExprState *) lfirst(fc_indexpr_item),
											   GetPerTupleExprContext(fc_estate),
											   &fc_isNull);
			fc_indexpr_item = lnext(fc_indexInfo->ii_ExpressionsState, fc_indexpr_item);
		}
		fc_values[fc_i] = fc_iDatum;
		fc_isnull[fc_i] = fc_isNull;
	}

	if (fc_indexpr_item != NULL)
		elog(ERROR, "wrong number of index expressions");
}


/*
 * index_update_stats --- 在 CREATE INDEX 或 REINDEX 后更新 pg_class 条目
 *
 * 此例程在 CREATE INDEX 或 REINDEX 后更新索引或其父关系的 pg_class 行。其相当奇怪的 API 旨在确保我们可以在一次更新中完成所有必要的工作。
 *
 * hasindex: 将 relhasindex 设置为此值
 * reltuples: 如果 >= 0，将 reltuples 设置为此值；否则不改变
 *
 * 如果 reltuples >= 0，则 relpages 和 relallvisible 也会更新（使用 RelationGetNumberOfBlocks() 和 visibilitymap_count()）。
 *
 * 注意：此操作的重要副作用是发送 SI 无效消息给所有后端 --- 包括我 --- 导致 relcache 条目被刷新或更新为新数据。即使我们发现 pg_class 行不需要更改，这也必须发生。当更新堆条目时，这确保其他后端能够了解新索引。当更新索引时，这一点很重要，因为一些索引 AM 期望在 REINDEX 后发生 relcache 刷新。
 */
static void fc_index_update_stats(Relation fc_rel,
				   bool fc_hasindex,
				   double fc_reltuples)
{
	bool		update_stats;
	BlockNumber fc_relpages = 0;	/* 保持编译器安静 */
	BlockNumber fc_relallvisible = 0;
	Oid			fc_relid = RelationGetRelid(fc_rel);
	Relation	pg_class;
	ScanKeyData fc_key[1];
	HeapTuple	fc_tuple;
	void	   *fc_state;
	Form_pg_class fc_rd_rel;
	bool		fc_dirty;

	/*
	 * 作为特别的黑客，如果我们处理的是空表且现有 reltuples 为 -1，我们将保持不变。这确保了将索引作为 CREATE TABLE 的一部分不会导致表在未经过预处理的情况下看起来像是已经被清空。我们修改的 rd_rel 可能与 rel->rd_rel 不同，因为 e.g. 并发 GRANT 的提交，但更改 reltuples 的命令会与我们的锁发生冲突。（即使某个命令在较弱的锁下更改了 reltuples，这也仅影响空表的统计信息。）
	 */
	if (fc_reltuples == 0 && fc_rel->rd_rel->reltuples < 0)
		fc_reltuples = -1;

	update_stats = fc_reltuples >= 0;

	/*
	 * 在 systable_inplace_update_begin() 锁定 pg_class 缓冲区之前，完成 I/O 和可见性映射缓冲区锁。我们修改的 rd_rel 可能与 rel->rd_rel 不同，因为 e.g. 并发 GRANT 的提交，但没有命令将 relkind 从非索引更改为索引。（即使有一个，也不会影响 relallvisible 的功能。）
	 */
	if (update_stats)
	{
		fc_relpages = RelationGetNumberOfBlocks(fc_rel);

		if (fc_rel->rd_rel->relkind != RELKIND_INDEX)
			visibilitymap_count(fc_rel, &fc_relallvisible, NULL);
	}

	/*
	 * 我们总是使用非事务性的就地覆盖更新来更新 pg_class 行。
	 * 这样做有几个原因：
	 *
	 * 1. 在引导模式下，我们别无选择——UPDATE 无法工作。
	 *
	 * 2. 我们可能正在重新索引 pg_class 本身，在这种情况下我们不能移动
	 * 其 pg_class 行，因为 CatalogTupleInsert/CatalogTupleUpdate 可能
	 * 还不知道所有索引（见 reindex_relation）。
	 *
	 * 3. 因为我们在父关系上以共享锁执行 CREATE INDEX
	 * （以允许并发索引创建），普通更新可能会遭遇与其他
	 * CREATE INDEX 几乎同时提交的更新失败。我们可以通过让它们都执行
	 * 非事务性更新来避免这种情况（我们假设它们都试图将 pg_class 行
	 * 更改为相同的内容，因此谁先执行并不重要）。
	 *
	 * 使用非事务性更新是安全的，即使我们的
	 * 事务在提交之前仍然可能失败。即使没有索引，设置 relhasindex
	 * 为 true 也是安全的（VACUUM 最终会修复它）。当然，无论如何
	 * 新的 relpages 和 reltuples 计数都是正确的。然而，如果调用者没有
	 * 提供更新后的 reltuples 计数，我们不想更改 relpages（或
	 * relallvisible），因为那样会影响 reltuples/relpages 比率，
	 * 而这才是真正重要的。
	 */

	pg_class = table_open(RelationRelationId, RowExclusiveLock);

	ScanKeyInit(&fc_key[0],
				Anum_pg_class_oid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(fc_relid));
	systable_inplace_update_begin(pg_class, ClassOidIndexId, true, NULL,
								  1, fc_key, &fc_tuple, &fc_state);

	if (!HeapTupleIsValid(fc_tuple))
		elog(ERROR, "could not find tuple for relation %u", fc_relid);
	fc_rd_rel = (Form_pg_class) GETSTRUCT(fc_tuple);

	/* 这应该是一个更全面的测试吗？ */
	Assert(fc_rd_rel->relkind != RELKIND_PARTITIONED_INDEX);

	/* 如果有必要，对复制的元组应用所需的更新 */

	fc_dirty = false;
	if (fc_rd_rel->relhasindex != fc_hasindex)
	{
		fc_rd_rel->relhasindex = fc_hasindex;
		fc_dirty = true;
	}

	if (update_stats)
	{
		if (fc_rd_rel->relpages != (int32) fc_relpages)
		{
			fc_rd_rel->relpages = (int32) fc_relpages;
			fc_dirty = true;
		}
		if (fc_rd_rel->reltuples != (float4) fc_reltuples)
		{
			fc_rd_rel->reltuples = (float4) fc_reltuples;
			fc_dirty = true;
		}
		if (fc_rd_rel->relallvisible != (int32) fc_relallvisible)
		{
			fc_rd_rel->relallvisible = (int32) fc_relallvisible;
			fc_dirty = true;
		}
	}

	/*
	 * 如果有任何更改，写出元组
	 */
	if (fc_dirty)
	{
		systable_inplace_update_finish(fc_state, fc_tuple);
		/* 以上发送了一个缓存失效消息 */
	}
	else
	{
		systable_inplace_update_cancel(fc_state);
		/* 不需要更改元组，但无论如何强制 relcache 失效 */
		CacheInvalidateRelcacheByTuple(fc_tuple);
	}

	heap_freetuple(fc_tuple);

	table_close(pg_class, RowExclusiveLock);
}


/*
 * index_build - 调用特定于访问方法的索引构建过程
 *
 * 在入口时，索引的目录条目是有效的，并且其物理磁盘
 * 文件已被创建但为空。我们调用特定于 AM 的构建
 * 过程来填充索引内容。然后，我们根据需要更新
 * 索引和堆关系的 pg_class 条目，使用 ambuild 返回的统计信息
 * 以及调用者传递的数据。
 *
 * isreindex 指示我们正在重新创建一个已存在的索引。
 * parallel 指示并行是否可能有用。
 *
 * 注意：在 Postgres 8.2 之前，传入的堆和索引关系
 * 会被该例程自动关闭。现在已不是这样了。
 * 调用者打开了它们，调用者应该关闭它们。
 */
void index_build(Relation fc_heapRelation,
			Relation fc_indexRelation,
			IndexInfo *fc_indexInfo,
			bool fc_isreindex,
			bool fc_parallel)
{
	IndexBuildResult *fc_stats;
	Oid			fc_save_userid;
	int			fc_save_sec_context;
	int			fc_save_nestlevel;

	/*
	 * 健康检查
	 */
	Assert(RelationIsValid(fc_indexRelation));
	Assert(PointerIsValid(fc_indexRelation->rd_indam));
	Assert(PointerIsValid(fc_indexRelation->rd_indam->ambuild));
	Assert(PointerIsValid(fc_indexRelation->rd_indam->ambuildempty));

	/*
	 * 确定并行 CREATE INDEX 的工作进程详细信息。目前，
	 * 只有 btree 支持并行构建。
	 *
	 * 请注意，规划器会考虑并行安全性。
	 */
	if (fc_parallel && IsNormalProcessingMode() &&
		fc_indexRelation->rd_rel->relam == BTREE_AM_OID)
		fc_indexInfo->ii_ParallelWorkers =
			plan_create_index_workers(RelationGetRelid(fc_heapRelation),
									  RelationGetRelid(fc_indexRelation));

	if (fc_indexInfo->ii_ParallelWorkers == 0)
		ereport(DEBUG1,
				(errmsg_internal("building index \"%s\" on table \"%s\" serially",
								 RelationGetRelationName(fc_indexRelation),
								 RelationGetRelationName(fc_heapRelation))));
	else
		ereport(DEBUG1,
				(errmsg_internal("building index \"%s\" on table \"%s\" with request for %d parallel workers",
								 RelationGetRelationName(fc_indexRelation),
								 RelationGetRelationName(fc_heapRelation),
								 fc_indexInfo->ii_ParallelWorkers)));

	/*
	 * 切换到表所有者的用户ID，以便任何索引函数以该用户身份运行。
	 * 同时锁定安全性受限的操作，并安排将 GUC 变量更改限制在此命令中。
	 */
	GetUserIdAndSecContext(&fc_save_userid, &fc_save_sec_context);
	SetUserIdAndSecContext(fc_heapRelation->rd_rel->relowner,
						   fc_save_sec_context | SECURITY_RESTRICTED_OPERATION);
	fc_save_nestlevel = NewGUCNestLevel();

	/* 设置初始进度报告状态 */
	{
		const int	fc_progress_index[] = {
			PROGRESS_CREATEIDX_PHASE,
			PROGRESS_CREATEIDX_SUBPHASE,
			PROGRESS_CREATEIDX_TUPLES_DONE,
			PROGRESS_CREATEIDX_TUPLES_TOTAL,
			PROGRESS_SCAN_BLOCKS_DONE,
			PROGRESS_SCAN_BLOCKS_TOTAL
		};
		const int64 fc_progress_vals[] = {
			PROGRESS_CREATEIDX_PHASE_BUILD,
			PROGRESS_CREATEIDX_SUBPHASE_INITIALIZE,
			0, 0, 0, 0
		};

		pgstat_progress_update_multi_param(6, fc_progress_index, fc_progress_vals);
	}

	/*
	 * 调用访问方法的构建过程
	 */
	fc_stats = fc_indexRelation->rd_indam->ambuild(fc_heapRelation, fc_indexRelation,
											 fc_indexInfo);
	Assert(PointerIsValid(fc_stats));

	/*
	 * 如果这是一个不记录的索引，我们可能需要为其写出一个初始化分叉
	 * ——但我们必须首先检查是否已存在一个。例如，如果
	 * 在创建它的事务中，不记录的关系被截断，或在后续事务中被截断两次，
	 * relfilenode 不会改变，这里不需要做任何事情。
	 */
	if (fc_indexRelation->rd_rel->relpersistence == RELPERSISTENCE_UNLOGGED &&
		!smgrexists(RelationGetSmgr(fc_indexRelation), INIT_FORKNUM))
	{
		smgrcreate(RelationGetSmgr(fc_indexRelation), INIT_FORKNUM, false);
		log_smgrcreate(&fc_indexRelation->rd_node, INIT_FORKNUM);
		fc_indexRelation->rd_indam->ambuildempty(fc_indexRelation);
	}

	/*
	 * 如果我们发现任何可能损坏的 HOT 链，则标记索引为不可用，
	 * 直到当前事务位于事件视界以下。请参见 src/backend/access/heap/README.HOT 
	 * 进行讨论。如果对堆关系启用了早期修剪/清理，也要设置这一点。
	 * 虽然基于实际的清理活动和其他活跃事务，可能会更早变得安全使用索引，
	 * 但对此的测试会复杂得多，并需要某种形式的阻塞，因此通过仅使用当前事务来
	 * 保持简单和快速。
	 *
	 * 然而，在重新索引现有索引时，我们在这里不应做任何事情。
	 * 无论与索引相关的任何损坏的 HOT 链都必须发生在
	 * 索引的原始创建之前，因此没有必要更改
	 * 索引的可用性地平线。此外，在重新索引 pg_index 本身时，我们
	 * *绝对不能*尝试更改索引的 pg_index 条目，这一优化很巧妙地避免了这种情况。
	 * 重新索引所需的更复杂规则会在该函数返回后单独处理。
	 *
	 * 在并发索引构建期间，我们也不需要设置 indcheckxmin，
	 * 因为在所有关心损坏的 HOT 链或早期修剪/清理的事务结束之前，
	 * 我们不会将 indisvalid 设置为 true。
	 *
	 * 因此，这段代码只能在非并发的 CREATE INDEX 中执行。
	 * 因此，heap_update 设置 pg_index 元组的 xmin 是无关紧要的，
	 * 因为该元组的创建发生在当前事务中。那也意味着我们不需要担心
	 * 并发读取元组的任何情况；没有其他事务可以看到它。
	 */
	if ((fc_indexInfo->ii_BrokenHotChain || EarlyPruningEnabled(fc_heapRelation)) &&
		!fc_isreindex &&
		!fc_indexInfo->ii_Concurrent)
	{
		Oid			fc_indexId = RelationGetRelid(fc_indexRelation);
		Relation	pg_index;
		HeapTuple	fc_indexTuple;
		Form_pg_index fc_indexForm;

		pg_index = table_open(IndexRelationId, RowExclusiveLock);

		fc_indexTuple = SearchSysCacheCopy1(INDEXRELID,
										 ObjectIdGetDatum(fc_indexId));
		if (!HeapTupleIsValid(fc_indexTuple))
			elog(ERROR, "cache lookup failed for index %u", fc_indexId);
		fc_indexForm = (Form_pg_index) GETSTRUCT(fc_indexTuple);

		/* 如果这是一个新索引，则不应设置 indcheckxmin... */
		Assert(!fc_indexForm->indcheckxmin);

		fc_indexForm->indcheckxmin = true;
		CatalogTupleUpdate(pg_index, &fc_indexTuple->t_self, fc_indexTuple);

		heap_freetuple(fc_indexTuple);
		table_close(pg_index, RowExclusiveLock);
	}

	
/*
	 * 更新堆和索引 pg_class 行
	 */
	fc_index_update_stats(fc_heapRelation,
					   true,
					   fc_stats->heap_tuples);

	fc_index_update_stats(fc_indexRelation,
					   false,
					   fc_stats->index_tuples);

	/* 使更新后的目录行版本可见 */
	CommandCounterIncrement();

	/*
	 * 如果是为了排除约束，则对堆进行第二次遍历
	 * 以验证该约束是否满足。我们必须等到
	 * 索引完全有效后才能执行此操作。 （破损的 HOT 链条不应该影响，
	 * 详见 IndexCheckExclusion 的注释。）
	 */
	if (fc_indexInfo->ii_ExclusionOps != NULL)
		fc_IndexCheckExclusion(fc_heapRelation, fc_indexRelation, fc_indexInfo);

	/* 回滚索引函数执行的任何 GUC 更改 */
	AtEOXact_GUC(false, fc_save_nestlevel);

	/* 恢复 userid 和安全上下文 */
	SetUserIdAndSecContext(fc_save_userid, fc_save_sec_context);
}

/*
 * IndexCheckExclusion - 验证新的排除约束是否满足
 *
 * 在创建排除约束时，我们首先正常构建索引
 * 然后重新扫描堆以检查冲突。我们假设我们只
 * 需要验证根据最新快照处于活跃状态的元组，
 * 并且即使在存在破损的 HOT 链条的情况下，这些元组也
 * 被正确索引。这应该没问题，因为我们至少在该表上保持 ShareLock，
 * 意味着不能有来自其他事务的未提交更新。
 * （注意：这对于系统目录不一定有效，因为许多
 * 操作会在系统目录上提前释放写锁。）
 */
static void fc_IndexCheckExclusion(Relation fc_heapRelation,
					Relation fc_indexRelation,
					IndexInfo *fc_indexInfo)
{
	TableScanDesc fc_scan;
	Datum		fc_values[INDEX_MAX_KEYS];
	bool		fc_isnull[INDEX_MAX_KEYS];
	ExprState  *fc_predicate;
	TupleTableSlot *fc_slot;
	EState	   *fc_estate;
	ExprContext *fc_econtext;
	Snapshot	fc_snapshot;

	/*
	 * 如果我们正在重新索引目标索引，请将其标记为不再被重新
	 * 索引，以防在我们尝试使用该索引进行探测时造成
	 * index_beginscan 中的断言。这是可以的，因为
	 * 索引现在已完全有效。
	 */
	if (fc_ReindexIsCurrentlyProcessingIndex(RelationGetRelid(fc_indexRelation)))
		fc_ResetReindexProcessing();

	/*
	 * 需要一个EState来评估索引表达式和部分索引谓词。 还需要一个槽来保存当前元组。
	 */
	fc_estate = CreateExecutorState();
	fc_econtext = GetPerTupleExprContext(fc_estate);
	fc_slot = table_slot_create(fc_heapRelation, NULL);

	/* 安排econtext的扫描元组为待测试的元组 */
	fc_econtext->ecxt_scantuple = fc_slot;

	/* 如果有的话，为谓词设置执行状态。 */
	fc_predicate = ExecPrepareQual(fc_indexInfo->ii_Predicate, fc_estate);

	/*
	 * 扫描基本关系中的所有活跃元组。
	 */
	fc_snapshot = RegisterSnapshot(GetLatestSnapshot());
	fc_scan = table_beginscan_strat(fc_heapRelation,	/* 关系 */
								 fc_snapshot,	/* 快照 */
								 0, /* 键的数量 */
								 NULL,	/* 扫描键 */
								 true,	/* 缓冲区访问策略OK */
								 true); /* syncscan OK */

	while (table_scan_getnextslot(fc_scan, ForwardScanDirection, fc_slot))
	{
		CHECK_FOR_INTERRUPTS();

		/*
		 * 在部分索引中，忽略不满足谓词的元组。
		 */
		if (fc_predicate != NULL)
		{
			if (!ExecQual(fc_predicate, fc_econtext))
				continue;
		}

		/*
		 * 提取索引列值，包括计算表达式。
		 */
		FormIndexDatum(fc_indexInfo,
					   fc_slot,
					   fc_estate,
					   fc_values,
					   fc_isnull);

		/*
		 * 检查该元组是否没有冲突。
		 */
		check_exclusion_constraint(fc_heapRelation,
								   fc_indexRelation, fc_indexInfo,
								   &(fc_slot->tts_tid), fc_values, fc_isnull,
								   fc_estate, true);

		MemoryContextReset(fc_econtext->ecxt_per_tuple_memory);
	}

	table_endscan(fc_scan);
	UnregisterSnapshot(fc_snapshot);

	ExecDropSingleTupleTableSlot(fc_slot);

	FreeExecutorState(fc_estate);

	/* 这些可能指向现在已经消失的状态 */
	fc_indexInfo->ii_ExpressionsState = NIL;
	fc_indexInfo->ii_PredicateState = NULL;
}



/*
 * validate_index - 用于支持并发索引构建的辅助代码
 *
 * 我们通过首先插入索引的目录条目来进行并发索引构建，通过index_create()，标记它为既不indisready也不indisvalid。
 * 然后我们提交事务并开始一个新的事务，然后我们等待所有可能正在修改表的事务终止。现在我们知道，任何随后开始的事务都会看到索引并尊重其对HOT更新的约束；因此，虽然现有的HOT链可能与索引不一致，但当前存在的元组将不会被执行不兼容的HOT更新。我们现在通过index_build()正常构建索引，同时保持一个允许并发插入/更新/删除的弱锁。并且，我们只对自扫描开始时有效的元组进行索引（参见table_index_build_scan），而正常构建会特别处理最近死亡的元组。这是可以的，因为在所有可能能看到这些元组的事务结束之前，我们不会将索引标记为有效。这样做的原因是为了避免因并发UPDATE导致的虚假唯一索引失败（如果我们使用HeapTupleSatisfiesVacuum，当我们遍历它们时，我们可能会将同一行的不同版本视为有效）。这样我们就得到一个在构建索引时没有包含任何添加到表中的元组的索引。
 *
 * 接下来，我们将索引标记为“indisready”（但仍未“indisvalid”）并提交第二个事务，然后开始第三个事务。同样，我们等待所有可能正在修改表的事务终止。现在我们知道，任何随后开始的事务都会看到索引并将它们的新元组插入其中。然后我们拍摄一个新的引用快照，该快照传递给validate_index()。根据此快照有效但不在索引中的任何元组都必须被添加到索引中。（在快照之后提交的任何活动元组将由其原始事务插入索引。快照之前提交的任何死亡元组无需索引，因为在我们将索引标记为有效之前，我们会等待所有可能关心它们的事务结束。）
 *
 * validate_index()的工作原理是首先收集当前索引中的所有TID，使用一个bulkdelete回调，该回调仅存储TID并且不会说“删除它”。（这应该比普通的indexscan更快；此外，并非所有索引AM都支持全索引的indexscan。）然后我们对TID进行排序，最后扫描表并与TID列表进行“合并连接”，以查看哪些元组缺失在索引中。因此，我们将确保根据参考快照有效的所有元组都在索引中。
 *
 * 以这种方式构建唯一索引是棘手的：我们可能会尝试插入一个已经死亡或正在被删除的元组，并且我们不能有对同一行的更新版本的唯一性失败。我们可以尝试检查元组以查看它是否已经死亡，并告诉index_insert()不要进行唯一性检查，但这仍然使我们面临对正在进行更新的竞争条件。为了处理这个问题，我们希望索引AM在声明唯一性错误之前重新检查即将插入的元组的存活状态。
 *
 * 完成validate_index()后，我们等待直到在引用快照时活动的所有事务都结束；这是为了确保没有任何事务的快照比引用快照更旧（因此可能能够看到我们未索引的元组）。然后我们将索引标记为“indisvalid”并提交。后续事务将能够在查询中使用它。
 *
 * 做两个完整的表扫描是一种蛮力策略。我们可以试着聪明一些，例如，在表的特殊区域中存储新元组（通过设置use_fsm可能使表变为仅追加）。但是这会增加更多的锁定问题。
 */
void validate_index(Oid fc_heapId, Oid fc_indexId, Snapshot fc_snapshot)
{
	Relation	fc_heapRelation,
				fc_indexRelation;
	IndexInfo  *fc_indexInfo;
	IndexVacuumInfo fc_ivinfo;
	ValidateIndexState fc_state;
	Oid			fc_save_userid;
	int			fc_save_sec_context;
	int			fc_save_nestlevel;

	{
		const int	fc_progress_index[] = {
			PROGRESS_CREATEIDX_PHASE,
			PROGRESS_CREATEIDX_TUPLES_DONE,
			PROGRESS_CREATEIDX_TUPLES_TOTAL,
			PROGRESS_SCAN_BLOCKS_DONE,
			PROGRESS_SCAN_BLOCKS_TOTAL
		};
		const int64 fc_progress_vals[] = {
			PROGRESS_CREATEIDX_PHASE_VALIDATE_IDXSCAN,
			0, 0, 0, 0
		};

		pgstat_progress_update_multi_param(5, fc_progress_index, fc_progress_vals);
	}

	/* 打开并锁定父堆关系 */
	fc_heapRelation = table_open(fc_heapId, ShareUpdateExclusiveLock);

	/*
	 * 切换到表所有者的用户ID，以便任何索引函数以该用户身份运行。
	 * 同时锁定安全性受限的操作，并安排将 GUC 变量更改限制在此命令中。
	 */
	GetUserIdAndSecContext(&fc_save_userid, &fc_save_sec_context);
	SetUserIdAndSecContext(fc_heapRelation->rd_rel->relowner,
						   fc_save_sec_context | SECURITY_RESTRICTED_OPERATION);
	fc_save_nestlevel = NewGUCNestLevel();

	fc_indexRelation = index_open(fc_indexId, RowExclusiveLock);

	/*
	 * 获取index_insert所需的信息。 （你可能会认为这应该从DefineIndex传入，但由于在之前的事务中已被构建，其副本早已消失。）
	 */
	fc_indexInfo = BuildIndexInfo(fc_indexRelation);

	/* 将构建标记为并发只是为了保持一致性 */
	fc_indexInfo->ii_Concurrent = true;

	/*
	 * 扫描索引并将所有TID收集到一个tuplesort对象中。
	 */
	fc_ivinfo.index = fc_indexRelation;
	fc_ivinfo.analyze_only = false;
	fc_ivinfo.report_progress = true;
	fc_ivinfo.estimated_count = true;
	fc_ivinfo.message_level = DEBUG2;
	fc_ivinfo.num_heap_tuples = fc_heapRelation->rd_rel->reltuples;
	fc_ivinfo.strategy = NULL;

	/*
	 * 将TID编码为int8值进行排序，而不是直接对项指针进行排序。这可以显著加快速度，主要是因为TID在所有平台上都是按引用传递的类型，而int8在大多数平台上是按值传递的。
	 */
	fc_state.tuplesort = tuplesort_begin_datum(INT8OID, Int8LessOperator,
											InvalidOid, false,
											maintenance_work_mem,
											NULL, TUPLESORT_NONE);
	fc_state.htups = fc_state.itups = fc_state.tups_inserted = 0;

	/* ambulkdelete更新进度指标 */
	(void) index_bulk_delete(&fc_ivinfo, NULL,
							 fc_validate_index_callback, (void *) &fc_state);

	/* 执行排序 */
	{
		const int	fc_progress_index[] = {
			PROGRESS_CREATEIDX_PHASE,
			PROGRESS_SCAN_BLOCKS_DONE,
			PROGRESS_SCAN_BLOCKS_TOTAL
		};
		const int64 fc_progress_vals[] = {
			PROGRESS_CREATEIDX_PHASE_VALIDATE_SORT,
			0, 0
		};

		pgstat_progress_update_multi_param(3, fc_progress_index, fc_progress_vals);
	}
	tuplesort_performsort(fc_state.tuplesort);

	/*
	 * 现在扫描堆并与索引“合并”
	 */
	pgstat_progress_update_param(PROGRESS_CREATEIDX_PHASE,
								 PROGRESS_CREATEIDX_PHASE_VALIDATE_TABLESCAN);
	table_index_validate_scan(fc_heapRelation,
							  fc_indexRelation,
							  fc_indexInfo,
							  fc_snapshot,
							  &fc_state);

	/* 完成元组排序对象 */
	tuplesort_end(fc_state.tuplesort);

	elog(DEBUG2,
		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
		 fc_state.htups, fc_state.itups, fc_state.tups_inserted);

	/* 回滚索引函数执行的任何 GUC 更改 */
	AtEOXact_GUC(false, fc_save_nestlevel);

	/* 恢复 userid 和安全上下文 */
	SetUserIdAndSecContext(fc_save_userid, fc_save_sec_context);

	/* 关闭关系，但保持锁定 */
	index_close(fc_indexRelation, NoLock);
	table_close(fc_heapRelation, NoLock);
}

/* 
 * validate_index_callback - 批量删除回调以收集索引 TIDs 
 */
static bool fc_validate_index_callback(ItemPointer fc_itemptr, void *fc_opaque)
{
	ValidateIndexState *fc_state = (ValidateIndexState *) fc_opaque;
	int64		fc_encoded = itemptr_encode(fc_itemptr);

	tuplesort_putdatum(fc_state->tuplesort, Int64GetDatum(fc_encoded), false);
	fc_state->itups += 1;
	return false;				/* 从未实际删除任何东西 */
}

/* 
 * index_set_state_flags - 调整 pg_index 状态标志 
 * 
 * 这在 CREATE/DROP INDEX CONCURRENTLY 期间用于调整 pg_index 
 * 标志，以表示索引的状态。
 * 
 * 请注意，CatalogTupleUpdate() 会为元组发送缓存失效消息，因此其他会话会在我们提交后立即听到更新的消息。
 */
void index_set_state_flags(Oid fc_indexId, IndexStateFlagsAction fc_action)
{
	Relation	pg_index;
	HeapTuple	fc_indexTuple;
	Form_pg_index fc_indexForm;

	/* 打开 pg_index 并获取索引元组的可写副本 */
	pg_index = table_open(IndexRelationId, RowExclusiveLock);

	fc_indexTuple = SearchSysCacheCopy1(INDEXRELID,
									 ObjectIdGetDatum(fc_indexId));
	if (!HeapTupleIsValid(fc_indexTuple))
		elog(ERROR, "cache lookup failed for index %u", fc_indexId);
	fc_indexForm = (Form_pg_index) GETSTRUCT(fc_indexTuple);

	/* 在副本上执行请求的状态更改 */
	switch (fc_action)
	{
		case INDEX_CREATE_SET_READY:
			/* 在 CREATE INDEX CONCURRENTLY 序列中设置 indisready */
			Assert(fc_indexForm->indislive);
			Assert(!fc_indexForm->indisready);
			Assert(!fc_indexForm->indisvalid);
			fc_indexForm->indisready = true;
			break;
		case INDEX_CREATE_SET_VALID:
			/* 在 CREATE INDEX CONCURRENTLY 序列中设置 indisvalid */
			Assert(fc_indexForm->indislive);
			Assert(fc_indexForm->indisready);
			Assert(!fc_indexForm->indisvalid);
			fc_indexForm->indisvalid = true;
			break;
		case INDEX_DROP_CLEAR_VALID:

			/* 
			 * 在 DROP INDEX CONCURRENTLY 序列中清除 indisvalid 
			 * 
			 * 如果 indisready == true，我们保持其设置，以便索引仍然可以
			 * 由活动事务维护。我们只需确保
			 * indisvalid 为 false。（我们不声称两个值最初都是
			 * true，因为我们希望能够重试部分失败的 DROP INDEX
			 * CONCURRENTLY。）
			 * 
			 * 注意：CLUSTER 逻辑假设 indisclustered 不能在任何无效索引上
			 * 设置，因此也清除该标志。为
			 * 整洁起见，清除 indisreplident。
			 */
			fc_indexForm->indisvalid = false;
			fc_indexForm->indisclustered = false;
			fc_indexForm->indisreplident = false;
			break;
		case INDEX_DROP_SET_DEAD:

			/* 
			 * 在 DROP INDEX CONCURRENTLY 期间清除 indisready/indislive 
			 * 
			 * 我们清除 indisready 和 indislive，因为我们不仅想
			 * 停止更新，还想防止会话接触
			 * 索引。
			 */
			Assert(!fc_indexForm->indisvalid);
			Assert(!fc_indexForm->indisclustered);
			Assert(!fc_indexForm->indisreplident);
			fc_indexForm->indisready = false;
			fc_indexForm->indislive = false;
			break;
	}

	/* ...并更新它 */
	CatalogTupleUpdate(pg_index, &fc_indexTuple->t_self, fc_indexTuple);

	table_close(pg_index, RowExclusiveLock);
}


/* 
 * IndexGetRelation：给定索引的关系 OID，获取它是
 * 索引的关系的 OID。使用系统缓存。
 */
Oid IndexGetRelation(Oid fc_indexId, bool fc_missing_ok)
{
	HeapTuple	fc_tuple;
	Form_pg_index fc_index;
	Oid			fc_result;

	fc_tuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(fc_indexId));
	if (!HeapTupleIsValid(fc_tuple))
	{
		if (fc_missing_ok)
			return InvalidOid;
		elog(ERROR, "cache lookup failed for index %u", fc_indexId);
	}
	fc_index = (Form_pg_index) GETSTRUCT(fc_tuple);
	Assert(fc_index->indexrelid == fc_indexId);

	fc_result = fc_index->indrelid;
	ReleaseSysCache(fc_tuple);
	return fc_result;
}

/* 
 * reindex_index - 此例程用于重新创建单个索引 */
void reindex_index(Oid fc_indexId, bool fc_skip_constraint_checks, char fc_persistence,
			  ReindexParams *fc_params)
{
	Relation	fc_iRel,
				fc_heapRelation;
	Oid			fc_heapId;
	Oid			fc_save_userid;
	int			fc_save_sec_context;
	int			fc_save_nestlevel;
	IndexInfo  *fc_indexInfo;
	volatile bool fc_skipped_constraint = false;
	PGRUsage	fc_ru0;
	bool		fc_progress = ((fc_params->options & REINDEXOPT_REPORT_PROGRESS) != 0);
	bool		set_tablespace = false;

	pg_rusage_init(&fc_ru0);

	/* 
	 * 打开并锁定父堆关系。由于我们只需要确保没有架构或数据更改正在进行，
	 * 因此 ShareLock 足够了。
	 */
	fc_heapId = IndexGetRelation(fc_indexId,
							  (fc_params->options & REINDEXOPT_MISSING_OK) != 0);
	/* 如果关系缺失，则离开 */
	if (!OidIsValid(fc_heapId))
		return;

	if ((fc_params->options & REINDEXOPT_MISSING_OK) != 0)
		fc_heapRelation = try_table_open(fc_heapId, ShareLock);
	else
		fc_heapRelation = table_open(fc_heapId, ShareLock);

	/* 如果关系不存在，则离开 */
	if (!fc_heapRelation)
		return;

	/*
	 * 切换到表所有者的用户ID，以便任何索引函数以该用户身份运行。
	 * 同时锁定安全性受限的操作，并安排将 GUC 变量更改限制在此命令中。
	 */
	GetUserIdAndSecContext(&fc_save_userid, &fc_save_sec_context);
	SetUserIdAndSecContext(fc_heapRelation->rd_rel->relowner,
						   fc_save_sec_context | SECURITY_RESTRICTED_OPERATION);
	fc_save_nestlevel = NewGUCNestLevel();

	if (fc_progress)
	{
		const int	fc_progress_cols[] = {
			PROGRESS_CREATEIDX_COMMAND,
			PROGRESS_CREATEIDX_INDEX_OID
		};
		const int64 fc_progress_vals[] = {
			PROGRESS_CREATEIDX_COMMAND_REINDEX,
			fc_indexId
		};

		pgstat_progress_start_command(PROGRESS_COMMAND_CREATE_INDEX,
									  fc_heapId);
		pgstat_progress_update_multi_param(2, fc_progress_cols, fc_progress_vals);
	}

	/* 
	 * 打开目标索引关系并获取独占锁，以确保没有其他
	 * 人触及这个特定的索引。
	 */
	if ((fc_params->options & REINDEXOPT_MISSING_OK) != 0)
		fc_iRel = try_index_open(fc_indexId, AccessExclusiveLock);
	else
		fc_iRel = index_open(fc_indexId, AccessExclusiveLock);

	/* 如果索引关系不存在，则离开 */
	if (!fc_iRel)
	{
		/* 回滚任何 GUC 更改 */
		AtEOXact_GUC(false, fc_save_nestlevel);

		/* 恢复 userid 和安全上下文 */
		SetUserIdAndSecContext(fc_save_userid, fc_save_sec_context);

		/* 关闭父堆关系，但保持锁定 */
		table_close(fc_heapRelation, NoLock);
		return;
	}

	if (fc_progress)
		pgstat_progress_update_param(PROGRESS_CREATEIDX_ACCESS_METHOD_OID,
									 fc_iRel->rd_rel->relam);

	/* 
	 * 分区索引不应在这里处理，因为它们没有
	 * 物理存储。
	 */
	if (fc_iRel->rd_rel->relkind == RELKIND_PARTITIONED_INDEX)
		elog(ERROR, "cannot reindex partitioned index \"%s.%s\"",
			 get_namespace_name(RelationGetNamespace(fc_iRel)),
			 RelationGetRelationName(fc_iRel));

	/* 
	 * 不允许对其他后端的临时表进行重新索引……它们的本地
	 * 缓存管理器无法应对。
	 */
	if (RELATION_IS_OTHER_TEMP(fc_iRel))
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("cannot reindex temporary tables of other sessions")));

	/* 
	 * 不允许对 TOAST 表上的无效索引进行重新索引。这是
	 * 残留物来自于失败的 REINDEX CONCURRENTLY，如果重建，
	 * 将无法再删除它。
	 */
	if (IsToastNamespace(RelationGetNamespace(fc_iRel)) &&
		!get_index_isvalid(fc_indexId))
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("cannot reindex invalid index on TOAST table")));

	/* 
	 * 系统关系即使在 enable_system_table_mods 开启的情况下也不能移动，以
	 * 保持与并发情况的一致性，其中关系的所有索引都是串行处理的，包括
	 * toast 关系的索引。
	 * 
	 * 请注意，此检查不是 CheckRelationTableSpaceMove() 的一部分，因为它
	 * 用于 ALTER TABLE SET TABLESPACE，可能会在
	 * toast 关系中级联。
	 */
	if (OidIsValid(fc_params->tablespaceOid) &&
		IsSystemRelation(fc_iRel))
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("cannot move system relation \"%s\"",
						RelationGetRelationName(fc_iRel))));

	/* 检查此索引的表空间是否需要更改 */
	if (OidIsValid(fc_params->tablespaceOid) &&
		CheckRelationTableSpaceMove(fc_iRel, fc_params->tablespaceOid))
		set_tablespace = true;

	/* 
	 * 还检查当前事务中对索引的活动使用；我们
	 * 不希望在打开的 indexscan 下面进行重新索引。
	 */
	CheckTableNotInUse(fc_iRel, "REINDEX INDEX");

	/* 如果请求，设置新表空间 */
	if (set_tablespace)
	{
		/* 更新其 pg_class 行 */
		SetRelationTableSpace(fc_iRel, fc_params->tablespaceOid, InvalidOid);

		/* 
		 * 在事务提交时安排解除旧索引存储的链接。
		 */
		RelationDropStorage(fc_iRel);
		RelationAssumeNewRelfilenode(fc_iRel);

		/* 确保reltablespace更改是可见的 */
		CommandCounterIncrement();
	}

	/* 
	 * 索引上的所有谓词锁即将失效。将它们提升
	 * 为堆上的关系锁。
	 */
	TransferPredicateLocksToHeapRelation(fc_iRel);

	/* 获取索引构建所需的信息 */
	fc_indexInfo = BuildIndexInfo(fc_iRel);

	/* 如果请求，跳过唯一性/排除约束检查 */
	if (fc_skip_constraint_checks)
	{
		if (fc_indexInfo->ii_Unique || fc_indexInfo->ii_ExclusionOps != NULL)
			fc_skipped_constraint = true;
		fc_indexInfo->ii_Unique = false;
		fc_indexInfo->ii_ExclusionOps = NULL;
		fc_indexInfo->ii_ExclusionProcs = NULL;
		fc_indexInfo->ii_ExclusionStrats = NULL;
	}

	/* 在重建目标索引时抑制使用目标索引 */
	fc_SetReindexProcessing(fc_heapId, fc_indexId);

	/* 为索引创建一个新的物理关系 */
	RelationSetNewRelfilenode(fc_iRel, fc_persistence);

	/* 初始化索引并重建 */
	/* 注意：我们不需要重新建立主键设置 */
	index_build(fc_heapRelation, fc_iRel, fc_indexInfo, true, true);

	/* 重新允许使用目标索引 */
	fc_ResetReindexProcessing();

	/*
	 * 如果索引标记为无效/未准备好/已死（即，它来自失败的
	 * CREATE INDEX CONCURRENTLY，或者 DROP INDEX CONCURRENTLY 中途失败），
	 * 并且我们没有跳过唯一性检查，我们现在可以将其标记为有效。
	 * 这允许在这种情况下使用 REINDEX 来清理。
	 *
	 * 我们也可以重置 indcheckxmin，因为我们现在已经进行了
	 * 非并发索引构建，*除了*在 index_build 找到一些仍然损坏的 HOT 链的情况。
	 * 如果发生这种情况，并且我们不必更改任何其他标志，我们只需保留 indcheckxmin 不变（请注意
	 * index_build 不会更改它，因为这是一个重建索引）。
	 * 这是可以接受和理想的，因为不更新元组将保持索引的可用性视野（记录为元组的 xmin 值）与之前相同。
	 *
	 * 但是，如果索引是无效/未准备好/已死，并且有损坏的 HOT
	 * 链，我们最好强制 indcheckxmin 为真，因为 HOT 链与索引不能冲突的
	 * 正常论点对于无效索引来说是可疑的。（如果索引已死，冲突肯定是可能的。
	 * 否则不应该发生，但我们还是要保持谨慎。）在这种情况下，推进可用性视野是合适的。
	 *
	 * 避免不必要更新的另一个原因是，在重建 pg_index 本身时，我们不能尝试更新其中的元组。
	 * pg_index 的索引在其干净状态下应始终具有这些标志，
	 * 因此不会发生这种情况。
	 *
	 * 如果对堆关系启用了早期修剪/清理，必须在每次构建或重建时将可用性视野推进到当前事务。
	 * 在这方面 pg_index 是可以的，因为目录表不受早期清理的影响。
	 */
	if (!fc_skipped_constraint)
	{
		Relation	pg_index;
		HeapTuple	fc_indexTuple;
		Form_pg_index fc_indexForm;
		bool		fc_index_bad;
		bool		fc_early_pruning_enabled = EarlyPruningEnabled(fc_heapRelation);

		pg_index = table_open(IndexRelationId, RowExclusiveLock);

		fc_indexTuple = SearchSysCacheCopy1(INDEXRELID,
										 ObjectIdGetDatum(fc_indexId));
		if (!HeapTupleIsValid(fc_indexTuple))
			elog(ERROR, "cache lookup failed for index %u", fc_indexId);
		fc_indexForm = (Form_pg_index) GETSTRUCT(fc_indexTuple);

		fc_index_bad = (!fc_indexForm->indisvalid ||
					 !fc_indexForm->indisready ||
					 !fc_indexForm->indislive);
		if (fc_index_bad ||
			(fc_indexForm->indcheckxmin && !fc_indexInfo->ii_BrokenHotChain) ||
			fc_early_pruning_enabled)
		{
			if (!fc_indexInfo->ii_BrokenHotChain && !fc_early_pruning_enabled)
				fc_indexForm->indcheckxmin = false;
			else if (fc_index_bad || fc_early_pruning_enabled)
				fc_indexForm->indcheckxmin = true;
			fc_indexForm->indisvalid = true;
			fc_indexForm->indisready = true;
			fc_indexForm->indislive = true;
			CatalogTupleUpdate(pg_index, &fc_indexTuple->t_self, fc_indexTuple);

			/*
			 * 使表的 relcache 无效，因此在我们提交后
			 * 所有会话将刷新表的索引列表。这确保了
			 * 如果有人在此更新期间未看到 pg_index 行，他们将在尝试
			 * 对表进行任何更新之前刷新他们的列表。
			 */
			CacheInvalidateRelcache(fc_heapRelation);
		}

		table_close(pg_index, RowExclusiveLock);
	}

	/* 记录我们做过的事情 */
	if ((fc_params->options & REINDEXOPT_VERBOSE) != 0)
		ereport(INFO,
				(errmsg("index \"%s\" was reindexed",
						get_rel_name(fc_indexId)),
				 errdetail_internal("%s",
									pg_rusage_show(&fc_ru0))));

	/* 回滚索引函数执行的任何 GUC 更改 */
	AtEOXact_GUC(false, fc_save_nestlevel);

	/* 恢复 userid 和安全上下文 */
	SetUserIdAndSecContext(fc_save_userid, fc_save_sec_context);

	/* 关闭关系，但保持锁定 */
	index_close(fc_iRel, NoLock);
	table_close(fc_heapRelation, NoLock);

	if (fc_progress)
		pgstat_progress_end_command();
}

/*
 * reindex_relation - 此例程用于重新创建所有索引
 * 的关系（可选地也包括其 toast 关系，如果有的话）。
 *
 * "flags" 是一个位掩码，可以包括以下位的任何组合：
 *
 * REINDEX_REL_PROCESS_TOAST: 如果为真，则也处理 toast 表（如果有的话）。
 *
 * REINDEX_REL_SUPPRESS_INDEX_USE: 如果为真，则关系被 VACUUM FULL 或 CLUSTER 等操作完全重建，
 * 因此其索引与之不一致。如果关系是系统目录，我们可能在重建索引过程中咨询，
 * 这会使事情变得麻烦。为了处理这种情况，我们将所有索引标记为待重建，
 * 以便在重建之前不信任它们。调用者必须在未通过执行 CommandCounterIncrement
 * 使重建的表可见的情况下调用我们；我们将在收集索引列表后执行 CCI。
 * （这样我们在收集列表时仍然可以使用目录索引。）
 *
 * REINDEX_REL_CHECK_CONSTRAINTS: 如果为真，重新检查唯一性和排除
 * 约束条件，否则不检查。为了避免死锁，对系统目录进行 VACUUM FULL 或
 * CLUSTER 必须省略此标志。如果怀疑约束不一致，则应使用 REINDEX
 * 来重建索引。为了获得最佳性能，其他调用者应在以可能导致约束有效性
 * 变化的方式变换数据后再包含该标志。
 *
 * REINDEX_REL_FORCE_INDEXES_UNLOGGED: 如果为真，将重建索引的持久性设置为
 * 不记录。
 *
 * REINDEX_REL_FORCE_INDEXES_PERMANENT: 如果为真，将重建索引的持久性设置为
 * 永久。
 *
 * 如果重建了任何索引（包括相关的 toast 表索引），则返回 true。
 * 请注意，在每次索引重建后将发生 CommandCounterIncrement。
 */
bool reindex_relation(Oid fc_relid, int fc_flags, ReindexParams *fc_params)
{
	Relation	fc_rel;
	Oid			fc_toast_relid;
	List	   *fc_indexIds;
	char		fc_persistence;
	bool		fc_result;
	ListCell   *fc_indexId;
	int			fc_i;

	/*
	 * 打开并锁定关系。ShareLock 足够，因为我们只需要
	 * 防止其中的模式和数据更改。此处使用的锁级别
	 * 应与 ReindexTable() 匹配。
	 */
	if ((fc_params->options & REINDEXOPT_MISSING_OK) != 0)
		fc_rel = try_table_open(fc_relid, ShareLock);
	else
		fc_rel = table_open(fc_relid, ShareLock);

	/* 如果关系不存在，则离开 */
	if (!fc_rel)
		return false;

	/*
	 * 分区表在此处永远不应被处理，因为它们没有
	 * 物理存储。
	 */
	if (fc_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
		elog(ERROR, "cannot reindex partitioned table \"%s.%s\"",
			 get_namespace_name(RelationGetNamespace(fc_rel)),
			 RelationGetRelationName(fc_rel));

	fc_toast_relid = fc_rel->rd_rel->reltoastrelid;

	/*
	 * 获取此关系的索引 OID 列表。 （我们相信 relcache 
	 * 会通过顺序扫描来获取此内容，如果忽略系统
	 * 索引的话。）
	 */
	fc_indexIds = RelationGetIndexList(fc_rel);

	if (fc_flags & REINDEX_REL_SUPPRESS_INDEX_USE)
	{
		/* 抑制使用所有索引，直到它们被重建 */
		fc_SetReindexPending(fc_indexIds);

		/*
		 * 使新的堆内容可见 --- 现在事情可能是不一致的！
		 */
		CommandCounterIncrement();
	}

	/*
	 * 计算索引的持久性：与拥有关系的持久性相同，除非调用者
	 * 另有指定。
	 */
	if (fc_flags & REINDEX_REL_FORCE_INDEXES_UNLOGGED)
		fc_persistence = RELPERSISTENCE_UNLOGGED;
	else if (fc_flags & REINDEX_REL_FORCE_INDEXES_PERMANENT)
		fc_persistence = RELPERSISTENCE_PERMANENT;
	else
		fc_persistence = fc_rel->rd_rel->relpersistence;

	/* 重建所有索引。 */
	fc_i = 1;
	foreach(fc_indexId, fc_indexIds)
	{
		Oid			fc_indexOid = lfirst_oid(fc_indexId);
		Oid			fc_indexNamespaceId = get_rel_namespace(fc_indexOid);

		
/*
		 * 跳过TOAST表中的任何无效索引。这些只能是
		 * 从失败的REINDEX CONCURRENTLY中残留的重复项，如果
		 * 重建它们将无法再删除。
		 */
		if (IsToastNamespace(fc_indexNamespaceId) &&
			!get_index_isvalid(fc_indexOid))
		{
			ereport(WARNING,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("cannot reindex invalid index \"%s.%s\" on TOAST table, skipping",
							get_namespace_name(fc_indexNamespaceId),
							get_rel_name(fc_indexOid))));

			/*
			 * 从重建待处理列表中移除这个无效的TOAST索引，
			 * 因为在这里跳过它是由于重建索引时会发生的严重故障，
			 * 如果我们尝试处理它。
			 */
			if (fc_flags & REINDEX_REL_SUPPRESS_INDEX_USE)
				fc_RemoveReindexPending(fc_indexOid);
			continue;
		}

		reindex_index(fc_indexOid, !(fc_flags & REINDEX_REL_CHECK_CONSTRAINTS),
					  fc_persistence, fc_params);

		CommandCounterIncrement();

		/* 索引不再应该在待处理列表中 */
		Assert(!ReindexIsProcessingIndex(fc_indexOid));

		/* 设置索引重建计数 */
		pgstat_progress_update_param(PROGRESS_CLUSTER_INDEX_REBUILD_COUNT,
									 fc_i);
		fc_i++;
	}

	/*
	 * 关闭关系，但继续保持锁定。
	 */
	table_close(fc_rel, NoLock);

	fc_result = (fc_indexIds != NIL);

	/*
	 * 如果该关系有一个次要的TOAST关系，在我们
	 * 仍然保持主表锁时也要重建它。
	 */
	if ((fc_flags & REINDEX_REL_PROCESS_TOAST) && OidIsValid(fc_toast_relid))
	{
		/*
		 * 注意，如果缺少TOAST关系，这将失败，因此
		 * 重置REINDEXOPT_MISSING_OK。即使为
		 * 父关系设置了新的表空间，其TOAST表上的索引也不会移动。
		 * 这个规则是通过将tablespaceOid设置为InvalidOid来强制执行的。
		 */
		ReindexParams fc_newparams = *fc_params;

		fc_newparams.options &= ~(REINDEXOPT_MISSING_OK);
		fc_newparams.tablespaceOid = InvalidOid;
		fc_result |= reindex_relation(fc_toast_relid, fc_flags, &fc_newparams);
	}

	return fc_result;
}


/* ----------------------------------------------------------------
 *		系统索引重建支持
 *
 * 当我们忙于重建系统索引时，此代码提供支持
 * 确保目录查找不使用该索引。我们还利用
 * 这一点来捕获在重建这些索引期间尝试使用用户索引的情况。
 * 该信息会传播给并行工作者；
 * 在并行操作期间尝试更改它是不允许的。
 * ----------------------------------------------------------------
 */

static Oid	currentlyReindexedHeap = InvalidOid;
static Oid	currentlyReindexedIndex = InvalidOid;
static List *pendingReindexedIndexes = NIL;
static int	reindexingNestLevel = 0;

/*
 * ReindexIsProcessingHeap
 *		如果由OID指定的堆当前正在被重建，则为真。
 */
bool ReindexIsProcessingHeap(Oid fc_heapOid)
{
	return fc_heapOid == currentlyReindexedHeap;
}

/*
 * ReindexIsCurrentlyProcessingIndex
 *		如果由OID指定的索引当前正在被重建，则为真。
 */
static bool fc_ReindexIsCurrentlyProcessingIndex(Oid fc_indexOid)
{
	return fc_indexOid == currentlyReindexedIndex;
}

/*
 * ReindexIsProcessingIndex
 *		如果由OID指定的索引当前正在被重建，
 *		或者应该被视为无效，因为它在等待重建。
 */
bool ReindexIsProcessingIndex(Oid fc_indexOid)
{
	return fc_indexOid == currentlyReindexedIndex ||
		list_member_oid(pendingReindexedIndexes, fc_indexOid);
}

/*
 * SetReindexProcessing
 *		设置标志，指定堆/索引正在被重建。
 */
static void fc_SetReindexProcessing(Oid fc_heapOid, Oid fc_indexOid)
{
	Assert(OidIsValid(fc_heapOid) && OidIsValid(fc_indexOid));
	/* 重建是不具可重入性的。 */
	if (OidIsValid(currentlyReindexedHeap))
		elog(ERROR, "cannot reindex while reindexing");
	currentlyReindexedHeap = fc_heapOid;
	currentlyReindexedIndex = fc_indexOid;
	/* 索引不再是“待处理”的重建。 */
	fc_RemoveReindexPending(fc_indexOid);
	/* 这可能已经被设置过，但以防万一，现在设置。 */
	reindexingNestLevel = GetCurrentTransactionNestLevel();
}

/*
 * ResetReindexProcessing
 *		取消设置重建状态。
 */
static void fc_ResetReindexProcessing(void)
{
	currentlyReindexedHeap = InvalidOid;
	currentlyReindexedIndex = InvalidOid;
	/* reindexingNestLevel 在 (sub)transaction 结束前保持设置 */
}

/*
 * SetReindexPending
 *		标记给定索引为待重建.
 *
 * 注意: 我们假设当前的内存上下文在整个过程中保持有效.
 */
static void fc_SetReindexPending(List *fc_indexes)
{
	/* 重建是不具可重入性的。 */
	if (pendingReindexedIndexes)
		elog(ERROR, "cannot reindex while reindexing");
	if (IsInParallelMode())
		elog(ERROR, "cannot modify reindex state during a parallel operation");
	pendingReindexedIndexes = list_copy(fc_indexes);
	reindexingNestLevel = GetCurrentTransactionNestLevel();
}

/*
 * RemoveReindexPending
 *		从待处理列表中移除给定索引.
 */
static void fc_RemoveReindexPending(Oid fc_indexOid)
{
	if (IsInParallelMode())
		elog(ERROR, "cannot modify reindex state during a parallel operation");
	pendingReindexedIndexes = list_delete_oid(pendingReindexedIndexes,
											  fc_indexOid);
}

/*
 * ResetReindexState
 *		在 (sub)transaction 回滚期间清除所有重建状态.
 */
void ResetReindexState(int fc_nestLevel)
{
	/*
	 * 由于重建不是可重入的，我们不需要处理嵌套的
	 * 重建状态。我们只需避免在 REINDEX 内部的子事务失败的情况下
	 * 混乱外层的状态。因此，检查当前的嵌套级别与重建操作的嵌套级别
	 * 是足够的.
	 */
	if (reindexingNestLevel >= fc_nestLevel)
	{
		currentlyReindexedHeap = InvalidOid;
		currentlyReindexedIndex = InvalidOid;

		/*
		 * 我们无须尝试释放 pendingReindexedIndexes 的内容;
		 * 该列表应在事务生命周期的上下文中，因此它将
		 * 自动消失.
		 */
		pendingReindexedIndexes = NIL;

		reindexingNestLevel = 0;
	}
}

/*
 * EstimateReindexStateSpace
 *		估算传递重建状态给并行工作者所需的空间.
 */
Size EstimateReindexStateSpace(void)
{
	return offsetof(SerializedReindexState, pendingReindexedIndexes)
		+ mul_size(sizeof(Oid), list_length(pendingReindexedIndexes));
}

/*
 * SerializeReindexState
 *		序列化重建状态以供并行工作者使用.
 */
void SerializeReindexState(Size fc_maxsize, char *fc_start_address)
{
	SerializedReindexState *fc_sistate = (SerializedReindexState *) fc_start_address;
	int			fc_c = 0;
	ListCell   *fc_lc;

	fc_sistate->currentlyReindexedHeap = currentlyReindexedHeap;
	fc_sistate->currentlyReindexedIndex = currentlyReindexedIndex;
	fc_sistate->numPendingReindexedIndexes = list_length(pendingReindexedIndexes);
	foreach(fc_lc, pendingReindexedIndexes)
		fc_sistate->pendingReindexedIndexes[fc_c++] = lfirst_oid(fc_lc);
}

/*
 * RestoreReindexState
 *		在并行工作者中恢复重建状态.
 */
void RestoreReindexState(void *fc_reindexstate)
{
	SerializedReindexState *fc_sistate = (SerializedReindexState *) fc_reindexstate;
	int			fc_c = 0;
	MemoryContext fc_oldcontext;

	currentlyReindexedHeap = fc_sistate->currentlyReindexedHeap;
	currentlyReindexedIndex = fc_sistate->currentlyReindexedIndex;

	Assert(pendingReindexedIndexes == NIL);
	fc_oldcontext = MemoryContextSwitchTo(TopMemoryContext);
	for (fc_c = 0; fc_c < fc_sistate->numPendingReindexedIndexes; ++fc_c)
		pendingReindexedIndexes =
			lappend_oid(pendingReindexedIndexes,
						fc_sistate->pendingReindexedIndexes[fc_c]);
	MemoryContextSwitchTo(fc_oldcontext);

	/* 请注意，工作者有自己的事务嵌套级别 */
	reindexingNestLevel = GetCurrentTransactionNestLevel();
}
